Fully deprecate old recoil (#18210)

## Summary

Removes the `recoil` dependency entirely from `package.json` and
`twenty-front/package.json`, completing the migration to Jotai as the
sole state management library.

Removes all Recoil infrastructure: `RecoilRoot` wrapper from `App.tsx`
and test decorators, `RecoilDebugObserver`, Recoil-specific ESLint rules
(`use-getLoadable-and-getValue-to-get-atoms`,
`useRecoilCallback-has-dependency-array`), and legacy Recoil utility
hooks/types (`useRecoilComponentState`, `useRecoilComponentValue`,
`createComponentState`, `createFamilyState`, `getSnapshotValue`,
`cookieStorageEffect`, `localStorageEffect`, etc.).

Renames all `V2`-suffixed Jotai state files and types to their canonical
names (e.g., `ComponentStateV2` -> `ComponentState`,
`agentChatInputStateV2` -> `agentChatInputState`, `SelectorCallbacksV2`
-> `SelectorCallbacks`), and removes the now-redundant V1 counterparts.

Updates ~433 files across the codebase to use the renamed Jotai imports,
remove Recoil imports, and clean up test wrappers (`RecoilRootDecorator`
-> `JotaiRootDecorator`).
This commit is contained in:
Charles Bochet 2026-02-25 12:26:42 +01:00 committed by GitHub
parent ecb7270a7b
commit 121788c42f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
439 changed files with 1714 additions and 3929 deletions

View file

@ -22,7 +22,7 @@ This directory contains Twenty's development guidelines and best practices in th
### React Development
- **react-general-guidelines.mdc** - Core React development principles (Auto-attached to React files)
- **react-state-management.mdc** - State management approaches with Recoil (Auto-attached to state files)
- **react-state-management.mdc** - State management approaches with Jotai (Auto-attached to state files)
### Testing & Quality
- **testing-guidelines.mdc** - Testing strategies and best practices (Auto-attached to test files)

View file

@ -7,7 +7,7 @@ alwaysApply: true
# Twenty Architecture
## Tech Stack
- **Frontend**: React 18, TypeScript, Recoil, Styled Components, Vite
- **Frontend**: React 18, TypeScript, Jotai, Styled Components, Vite
- **Backend**: NestJS, TypeORM, PostgreSQL, Redis, GraphQL
- **Monorepo**: Nx workspace with yarn

View file

@ -4,16 +4,20 @@ alwaysApply: false
---
# React State Management
## Recoil Patterns
## Jotai Patterns
```typescript
// ✅ Atoms for primitive state
export const currentUserState = atom<User | null>({
// ✅ Atoms for primitive state (use createAtomState for keyed state with optional persistence)
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const currentUserState = createAtomState<User | null>({
key: 'currentUserState',
default: null,
defaultValue: null,
});
// ✅ Selectors for derived state
export const userDisplayNameSelector = selector({
// ✅ Derived atoms for computed state (use createAtomSelector)
import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector';
export const userDisplayNameSelector = createAtomSelector({
key: 'userDisplayNameSelector',
get: ({ get }) => {
const user = get(currentUserState);
@ -21,13 +25,30 @@ export const userDisplayNameSelector = selector({
},
});
// ✅ Atom families for dynamic atoms
export const userByIdState = atomFamily<User | null, string>({
// ✅ Atom factory pattern for dynamic atoms (use createAtomFamilyState)
import { createAtomFamilyState } from '@/ui/utilities/state/jotai/utils/createAtomFamilyState';
export const userByIdState = createAtomFamilyState<User | null, string>({
key: 'userByIdState',
default: null,
defaultValue: null,
});
```
## Jotai Hooks
```typescript
// useAtomState - read and write (like useRecoilState)
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
// useAtomStateValue - read only (like useRecoilValue)
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
// useSetAtomState - write only (like useSetRecoilState)
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
```
## Provider
Jotai works without a Provider by default (unlike Recoil's RecoilRoot). For scoped stores or testing, use `Provider` from `jotai`.
## Local State Guidelines
```typescript
// ✅ Multiple useState for unrelated state
@ -74,7 +95,7 @@ const increment = useCallback(() => {
```
## Performance Tips
- Use atom families for dynamic data collections
- Implement proper selector caching
- Avoid heavy computations in selectors
- Use atom factory pattern (createAtomFamilyState) for dynamic data collections
- Derived atoms (createAtomSelector) are automatically memoized by Jotai
- Avoid heavy computations in derived atoms
- Batch state updates when possible

View file

@ -90,7 +90,7 @@ npx nx run twenty-front:graphql:generate --configuration=metadata
## Architecture Overview
### Tech Stack
- **Frontend**: React 18, TypeScript, Recoil (state management), Emotion (styling), Vite
- **Frontend**: React 18, TypeScript, Jotai (state management), Emotion (styling), Vite
- **Backend**: NestJS, TypeORM, PostgreSQL, Redis, GraphQL (with GraphQL Yoga)
- **Monorepo**: Nx workspace managed with Yarn 4
@ -138,7 +138,7 @@ packages/
- Multi-line comments use multiple `//` lines, not `/** */`
### State Management
- **Recoil** for global state: atoms for primitive state, selectors for derived state, atom families for dynamic collections
- **Jotai** for global state: atoms for primitive state, selectors for derived state, atom families for dynamic collections
- Component-specific state with React hooks (`useState`, `useReducer` for complex logic)
- GraphQL cache managed by Apollo Client
- Use functional state updates: `setState(prev => prev + 1)`

View file

@ -48,7 +48,6 @@
"react-responsive": "^9.0.2",
"react-router-dom": "^6.4.4",
"react-tooltip": "^5.13.1",
"recoil": "^0.7.7",
"remark-gfm": "^3.0.1",
"rxjs": "^7.2.0",
"semver": "^7.5.4",

View file

@ -125,12 +125,7 @@ export default [
// React hooks rules
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': [
'warn',
{
additionalHooks: 'useRecoilCallback',
},
],
'react-hooks/exhaustive-deps': 'warn',
// Lingui - detect untranslated strings
'lingui/no-unlocalized-strings': [
'error',
@ -246,7 +241,7 @@ export default [
'componentInstanceId',
'hotkeyScope',
'dropdownId',
'recoilScopeId',
'modalId',
'dialogId',
'itemId',
@ -411,15 +406,6 @@ export default [
// Navigation
'useNavigationSection',
// Recoil
'atom',
'atomFamily',
'selector',
'selectorFamily',
'useSetRecoilState',
'useRecoilState',
'useRecoilValue',
// GraphQL operations
'gql',
'useQuery',
@ -578,8 +564,7 @@ export default [
'twenty/no-state-useref': 'error',
'twenty/component-props-naming': 'error',
'twenty/explicit-boolean-predicates-in-if': 'error',
'twenty/use-getLoadable-and-getValue-to-get-atoms': 'error',
'twenty/useRecoilCallback-has-dependency-array': 'error',
'twenty/no-navigate-prefer-link': 'error',
},
},

View file

@ -58,14 +58,8 @@ import {
rule as styledComponentsPrefixedWithStyled,
RULE_NAME as styledComponentsPrefixedWithStyledName,
} from './rules/styled-components-prefixed-with-styled';
import {
rule as useGetLoadableAndGetValueToGetAtoms,
RULE_NAME as useGetLoadableAndGetValueToGetAtomsName,
} from './rules/use-getLoadable-and-getValue-to-get-atoms';
import {
rule as useRecoilCallbackHasDependencyArray,
RULE_NAME as useRecoilCallbackHasDependencyArrayName,
} from './rules/useRecoilCallback-has-dependency-array';
/**
* Import your custom workspace rules at the top of this file.
@ -102,11 +96,7 @@ module.exports = {
[styledComponentsPrefixedWithStyledName]:
styledComponentsPrefixedWithStyled,
[explicitBooleanPredicatesInIfName]: explicitBooleanPredicatesInIf,
[useGetLoadableAndGetValueToGetAtomsName]:
useGetLoadableAndGetValueToGetAtoms,
[maxConstsPerFileName]: maxConstsPerFile,
[useRecoilCallbackHasDependencyArrayName]:
useRecoilCallbackHasDependencyArray,
[noNavigatePreferLinkName]: noNavigatePreferLink,
[injectWorkspaceRepositoryName]: injectWorkspaceRepository,
[restApiMethodsShouldBeGuardedName]: restApiMethodsShouldBeGuarded,

View file

@ -1,54 +1,38 @@
import { TSESLint } from '@typescript-eslint/utils';
import { rule, RULE_NAME } from './matching-state-variable';
const ruleTester = new TSESLint.RuleTester({
parser: require.resolve('@typescript-eslint/parser'),
});
const ruleTester = new TSESLint.RuleTester();
ruleTester.run(RULE_NAME, rule, {
valid: [
{
code: 'const variable = useRecoilValue(variableState);',
code: 'const variable = useAtomStateValue(variableState);',
},
{
code: 'const variable = useRecoilScopedValue(variableScopedState);',
code: 'const [variable, setVariable] = useAtomState(variableState);',
},
// Component/Family hooks are not checked by this rule
{
code: 'const anything = useAtomComponentStateValue(variableComponentState);',
},
{
code: 'const [variable, setVariable] = useRecoilState(variableScopedState);',
code: 'const anything = useAtomFamilyStateValue(variableFamilyState);',
},
{
code: 'const [variable, setVariable] = useRecoilScopedState(variableScopedState);',
},
{
code: 'const [variable, setVariable] = useRecoilFamilyState(variableScopedState);',
},
{
code: 'const [variable, setVariable] = useRecoilScopedFamilyState(variableScopedState);',
code: 'const [anything, setAnything] = useAtomComponentFamilyState(variableComponentFamilyState);',
},
],
invalid: [
{
code: 'const myValue = useRecoilValue(variableState);',
code: 'const myValue = useAtomStateValue(variableState);',
errors: [
{
messageId: 'invalidVariableName',
},
],
output: 'const variable = useRecoilValue(variableState);',
},
{
code: 'const myValue = useRecoilScopedValue(variableState);',
errors: [
{
messageId: 'invalidVariableName',
},
],
output: 'const variable = useRecoilScopedValue(variableState);',
},
{
code: 'const [myValue, setMyValue] = useRecoilState(variableState);',
code: 'const [myValue, setMyValue] = useAtomState(variableState);',
errors: [
{
messageId: 'invalidVariableName',
@ -57,122 +41,22 @@ ruleTester.run(RULE_NAME, rule, {
messageId: 'invalidSetterName',
},
],
output: 'const [variable, setVariable] = useRecoilState(variableState);',
},
{
code: 'const [myValue] = useRecoilState(variableState);',
code: 'const [myValue] = useAtomState(variableState);',
errors: [
{
messageId: 'invalidVariableName',
},
],
output: 'const [variable] = useRecoilState(variableState);',
},
{
code: 'const [, setMyValue] = useRecoilState(variableState);',
code: 'const [, setMyValue] = useAtomState(variableState);',
errors: [
{
messageId: 'invalidSetterName',
},
],
output: 'const [, setVariable] = useRecoilState(variableState);',
},
{
code: 'const [myValue, setMyValue] = useRecoilScopedState(variableState);',
errors: [
{
messageId: 'invalidVariableName',
},
{
messageId: 'invalidSetterName',
},
],
output:
'const [variable, setVariable] = useRecoilScopedState(variableState);',
},
{
code: 'const [myValue] = useRecoilScopedState(variableState);',
errors: [
{
messageId: 'invalidVariableName',
},
],
output: 'const [variable] = useRecoilScopedState(variableState);',
},
{
code: 'const [, setMyValue] = useRecoilScopedState(variableState);',
errors: [
{
messageId: 'invalidSetterName',
},
],
output: 'const [, setVariable] = useRecoilScopedState(variableState);',
},
{
code: 'const [myValue, setMyValue] = useRecoilFamilyState(variableState);',
errors: [
{
messageId: 'invalidVariableName',
},
{
messageId: 'invalidSetterName',
},
],
output:
'const [variable, setVariable] = useRecoilFamilyState(variableState);',
},
{
code: 'const [myValue] = useRecoilFamilyState(variableState);',
errors: [
{
messageId: 'invalidVariableName',
},
],
output: 'const [variable] = useRecoilFamilyState(variableState);',
},
{
code: 'const [, setMyValue] = useRecoilFamilyState(variableState);',
errors: [
{
messageId: 'invalidSetterName',
},
],
output: 'const [, setVariable] = useRecoilFamilyState(variableState);',
},
{
code: 'const [myValue, setMyValue] = useRecoilScopedFamilyState(variableState);',
errors: [
{
messageId: 'invalidVariableName',
},
{
messageId: 'invalidSetterName',
},
],
output:
'const [variable, setVariable] = useRecoilScopedFamilyState(variableState);',
},
{
code: 'const [myValue] = useRecoilScopedFamilyState(variableState);',
errors: [
{
messageId: 'invalidVariableName',
},
],
output: 'const [variable] = useRecoilScopedFamilyState(variableState);',
},
{
code: 'const [, setMyValue] = useRecoilScopedFamilyState(variableState);',
errors: [
{
messageId: 'invalidSetterName',
},
],
output:
'const [, setVariable] = useRecoilScopedFamilyState(variableState);',
},
],
});

View file

@ -14,8 +14,7 @@ export const rule = ESLintUtils.RuleCreator(() => __filename)({
type: 'problem',
docs: {
description:
'Ensure recoil value and setter are named after their atom name',
recommended: 'recommended',
'Ensure state value and setter are named after their atom name',
},
fixable: 'code',
schema: [],
@ -33,14 +32,9 @@ export const rule = ESLintUtils.RuleCreator(() => __filename)({
if (
node?.init?.type === AST_NODE_TYPES.CallExpression &&
isIdentifier(node.init.callee) &&
[
'useRecoilState',
'useRecoilScopedState',
'useRecoilFamilyState',
'useRecoilScopedFamilyState',
'useRecoilValue',
'useRecoilScopedValue',
].includes(node.init.callee.name)
['useAtomState', 'useAtomStateValue'].includes(
node.init.callee.name,
)
) {
const stateNameBase = isIdentifier(node.init.arguments[0])
? node.init.arguments[0].name
@ -51,7 +45,7 @@ export const rule = ESLintUtils.RuleCreator(() => __filename)({
}
const expectedVariableNameBase = stateNameBase.replace(
/(State|FamilyState|Selector|ScopedState|ScopedFamilyState|ScopedSelector)$/,
/(State|Selector|ScopedState|ScopedFamilyState|ScopedSelector)$/,
'',
);

View file

@ -45,7 +45,6 @@ const ALLOWED_TAGS = [
'AccordionGroup',
'Router',
'BrowserRouter',
'RecoilRoot',
];
export const rule: Rule.RuleModule = {

View file

@ -1,52 +0,0 @@
import { TSESLint } from '@typescript-eslint/utils';
import { rule, RULE_NAME } from './use-getLoadable-and-getValue-to-get-atoms';
const ruleTester = new TSESLint.RuleTester({
parser: require.resolve('@typescript-eslint/parser'),
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
});
ruleTester.run(RULE_NAME, rule, {
valid: [
{
code: 'const atoms = snapshot.getLoadable(someState).getValue();',
},
{
code: 'const atoms = snapshot.getLoadable(someState(viewId)).getValue();',
},
],
invalid: [
{
code: 'const atoms = await snapshot.getPromise(someState);',
errors: [
{
messageId: 'invalidAccessorOnSnapshot',
},
],
output: 'const atoms = await snapshot.getLoadable(someState);',
},
{
code: 'const atoms = await snapshot.getPromise(someState(viewId));',
errors: [
{
messageId: 'invalidAccessorOnSnapshot',
},
],
output: 'const atoms = await snapshot.getLoadable(someState(viewId));',
},
{
code: 'const atoms = snapshot.getLoadable(someState).anotherMethod();',
errors: [
{
messageId: 'invalidWayToGetAtoms',
},
],
output: 'const atoms = snapshot.getLoadable(someState).getValue();',
},
],
});

View file

@ -1,88 +0,0 @@
import { ESLintUtils } from '@typescript-eslint/utils';
// NOTE: The rule will be available in ESLint configs as "@nx/workspace-usage-getLoadable-and-getValue-to-get-atoms"
export const RULE_NAME = 'use-getLoadable-and-getValue-to-get-atoms';
export const rule = ESLintUtils.RuleCreator(() => __filename)({
name: RULE_NAME,
meta: {
type: 'problem',
docs: {
description: 'Ensure you are using getLoadable and getValue',
recommended: 'recommended',
},
fixable: 'code',
schema: [],
messages: {
redundantAwait: 'Redundant await on non-promise',
invalidAccessorOnSnapshot:
"Expected to use method 'getLoadable()' on 'snapshot' but instead found '{{ propertyName }}'",
invalidWayToGetAtoms:
"Expected to use method 'getValue()' with 'getLoadable()' but instead found '{{ propertyName }}'",
},
},
defaultOptions: [],
create: (context) => ({
AwaitExpression: (node) => {
const { argument, range }: any = node;
if (
(argument.callee?.object?.callee?.object?.name === 'snapshot' &&
argument?.callee?.object?.callee?.property?.name === 'getLoadable') ||
(argument.callee?.object?.name === 'snapshot' &&
argument?.callee?.property?.name === 'getLoadable')
) {
// remove await
context.report({
node,
messageId: 'redundantAwait',
data: {
propertyName: argument.callee.property.name,
},
fix: (fixer) => fixer.removeRange([range[0], range[0] + 5]),
});
}
},
MemberExpression: (node) => {
const { object, property }: any = node;
if (
object.callee?.type === 'MemberExpression' &&
object.callee.object?.name === 'snapshot' &&
object.callee.property?.name === 'getLoadable'
) {
const propertyName = property.name;
if (propertyName !== 'getValue') {
context.report({
node: property,
messageId: 'invalidWayToGetAtoms',
data: {
propertyName,
},
// replace the property with `getValue`
fix: (fixer) => fixer.replaceText(property, 'getValue'),
});
}
}
},
CallExpression: (node) => {
const { callee }: any = node;
if (
callee.type === 'MemberExpression' &&
callee.object?.name === 'snapshot' &&
callee.property?.name === 'getPromise'
) {
context.report({
node: callee.property,
messageId: 'invalidAccessorOnSnapshot',
data: {
propertyName: callee.property.name,
},
// Replace `getPromise` with `getLoadable`
fix: (fixer) => fixer.replaceText(callee.property, 'getLoadable'),
});
}
},
}),
});

View file

@ -1,29 +0,0 @@
import { TSESLint } from '@typescript-eslint/utils';
import { rule, RULE_NAME } from './useRecoilCallback-has-dependency-array';
const ruleTester = new TSESLint.RuleTester({
parser: require.resolve('@typescript-eslint/parser'),
});
ruleTester.run(RULE_NAME, rule, {
valid: [
{
code: 'const someValue = useRecoilCallback(() => () => {}, []);',
},
{
code: 'const someValue = useRecoilCallback(() => () => {}, [dependency]);',
},
],
invalid: [
{
code: 'const someValue = useRecoilCallback(({}) => () => {});',
errors: [
{
messageId: 'isNecessaryDependencyArray',
},
],
output: 'const someValue = useRecoilCallback(({}) => () => {}, []);',
},
],
});

View file

@ -1,46 +0,0 @@
import { ESLintUtils } from '@typescript-eslint/utils';
// NOTE: The rule will be available in ESLint configs as "@nx/workspace-useRecoilCallback-has-dependency-array"
export const RULE_NAME = 'useRecoilCallback-has-dependency-array';
export const rule = ESLintUtils.RuleCreator(() => __filename)({
name: RULE_NAME,
meta: {
type: 'problem',
docs: {
description: 'Ensure `useRecoilCallback` is used with a dependency array',
recommended: 'recommended',
},
schema: [],
messages: {
isNecessaryDependencyArray:
'Is necessary dependency array with useRecoilCallback',
},
fixable: 'code',
},
defaultOptions: [],
create: (context) => {
return {
CallExpression: (node) => {
const { callee } = node;
if (
callee.type === 'Identifier' &&
callee.name === 'useRecoilCallback'
) {
const depsArg = node.arguments;
if (depsArg.length === 1) {
context.report({
node: callee,
messageId: 'isNecessaryDependencyArray',
data: {
callee,
deps: depsArg[0],
},
fix: (fixer) => fixer.insertTextAfter(depsArg[0], ', []'),
});
}
}
},
};
},
});

View file

@ -110,7 +110,6 @@
"react-responsive": "^9.0.2",
"react-router-dom": "^6.4.4",
"react-textarea-autosize": "^8.4.1",
"recoil": "^0.7.7",
"remark-gfm": "^4.0.1",
"transliteration": "^2.3.5",
"twenty-sdk": "workspace:*",

View file

@ -64,7 +64,7 @@ const setupMockUseParams = (objectNamePlural?: string) => {
};
jest.mock('@/ui/utilities/state/jotai/hooks/useAtomStateValue');
const setupMockRecoil = (
const setupMockState = (
objectNamePlural?: string,
verifyEmailRedirectPath?: string,
calendarBookingPageId?: string | null,
@ -340,7 +340,7 @@ describe('usePageChangeEffectNavigateLocation', () => {
setupMockIsWorkspaceActivationStatusEqualsTo(isWorkspaceSuspended);
setupMockIsLogged(isLoggedIn);
setupMockUseParams(objectNamePluralFromParams);
setupMockRecoil(objectNamePluralFromMetadata, verifyEmailRedirectPath);
setupMockState(objectNamePluralFromMetadata, verifyEmailRedirectPath);
expect(usePageChangeEffectNavigateLocation()).toEqual(res);
},

View file

@ -7,7 +7,6 @@ import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/s
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useActiveWorkflowVersionsWithManualTrigger } from '@/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger';
import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion';
@ -31,16 +30,13 @@ export const useRunWorkflowRecordActions = ({
const store = useStore();
const { getIcon } = useIcons();
const { enqueueWarningSnackBar } = useSnackBar();
const { recordIndexId } = useRecordIndexIdFromCurrentContextStore();
const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
contextStoreTargetedRecordsRuleComponentState,
recordIndexId,
);
const isPageInEditMode = useAtomComponentStateValue(
contextStoreIsPageInEditModeComponentState,
recordIndexId,
);
const selectedRecordIds =

View file

@ -1,8 +1,6 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { renderHook } from '@testing-library/react';
import { type ReactNode } from 'react';
import { RecoilRoot } from 'recoil';
import { useRelatedRecordActions } from '@/action-menu/actions/record-agnostic-actions/hooks/useRelatedRecordActions';
jest.mock('@/object-metadata/hooks/useObjectMetadataItems', () => ({
@ -24,10 +22,6 @@ jest.mock('@/object-metadata/hooks/useObjectMetadataItems', () => ({
}),
}));
const Wrapper = ({ children }: { children: ReactNode }) => (
<RecoilRoot>{children}</RecoilRoot>
);
describe('useRelatedRecordActions', () => {
const mockGetIcon = jest.fn();
@ -42,14 +36,12 @@ describe('useRelatedRecordActions', () => {
updatableFields: [],
} as unknown as ObjectMetadataItem;
const { result } = renderHook(
() =>
useRelatedRecordActions({
sourceObjectMetadataItem: objectMetadataItem,
getIcon: mockGetIcon,
startPosition: 18,
}),
{ wrapper: Wrapper },
const { result } = renderHook(() =>
useRelatedRecordActions({
sourceObjectMetadataItem: objectMetadataItem,
getIcon: mockGetIcon,
startPosition: 18,
}),
);
expect(result.current).toEqual({});
@ -58,14 +50,12 @@ describe('useRelatedRecordActions', () => {
it('should return empty object when objectMetadataItem is undefined', () => {
const objectMetadataItem = undefined as unknown as ObjectMetadataItem;
const { result } = renderHook(
() =>
useRelatedRecordActions({
sourceObjectMetadataItem: objectMetadataItem,
getIcon: mockGetIcon,
startPosition: 18,
}),
{ wrapper: Wrapper },
const { result } = renderHook(() =>
useRelatedRecordActions({
sourceObjectMetadataItem: objectMetadataItem,
getIcon: mockGetIcon,
startPosition: 18,
}),
);
expect(result.current).toEqual({});
@ -104,14 +94,12 @@ describe('useRelatedRecordActions', () => {
updatableFields: fields,
} as unknown as ObjectMetadataItem;
const { result } = renderHook(
() =>
useRelatedRecordActions({
sourceObjectMetadataItem: objectMetadataItem,
getIcon: mockGetIcon,
startPosition: 18,
}),
{ wrapper: Wrapper },
const { result } = renderHook(() =>
useRelatedRecordActions({
sourceObjectMetadataItem: objectMetadataItem,
getIcon: mockGetIcon,
startPosition: 18,
}),
);
expect(Object.keys(result.current)).toHaveLength(2);
@ -156,14 +144,12 @@ describe('useRelatedRecordActions', () => {
updatableFields: fields,
} as unknown as ObjectMetadataItem;
const { result } = renderHook(
() =>
useRelatedRecordActions({
sourceObjectMetadataItem: objectMetadataItem,
getIcon: mockGetIcon,
startPosition: 18,
}),
{ wrapper: Wrapper },
const { result } = renderHook(() =>
useRelatedRecordActions({
sourceObjectMetadataItem: objectMetadataItem,
getIcon: mockGetIcon,
startPosition: 18,
}),
);
expect(Object.keys(result.current)).toHaveLength(1);
@ -204,14 +190,12 @@ describe('useRelatedRecordActions', () => {
updatableFields: fields,
} as unknown as ObjectMetadataItem;
const { result } = renderHook(
() =>
useRelatedRecordActions({
sourceObjectMetadataItem: objectMetadataItem,
getIcon: mockGetIcon,
startPosition: 18,
}),
{ wrapper: Wrapper },
const { result } = renderHook(() =>
useRelatedRecordActions({
sourceObjectMetadataItem: objectMetadataItem,
getIcon: mockGetIcon,
startPosition: 18,
}),
);
expect(result.current['create-related-person'].position).toBe(18);
@ -252,14 +236,12 @@ describe('useRelatedRecordActions', () => {
updatableFields: fields,
} as unknown as ObjectMetadataItem;
const { result } = renderHook(
() =>
useRelatedRecordActions({
sourceObjectMetadataItem: objectMetadataItem,
getIcon: mockGetIcon,
startPosition: 18,
}),
{ wrapper: Wrapper },
const { result } = renderHook(() =>
useRelatedRecordActions({
sourceObjectMetadataItem: objectMetadataItem,
getIcon: mockGetIcon,
startPosition: 18,
}),
);
expect(Object.keys(result.current)).toHaveLength(1);

View file

@ -2,7 +2,6 @@ import { Action } from '@/action-menu/actions/components/Action';
import { ActionScope } from '@/action-menu/actions/types/ActionScope';
import { ActionType } from '@/action-menu/actions/types/ActionType';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreIsPageInEditModeComponentState } from '@/context-store/states/contextStoreIsPageInEditModeComponentState';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useActiveWorkflowVersionsWithManualTrigger } from '@/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger';
@ -17,7 +16,6 @@ export const useRunWorkflowRecordAgnosticActions = () => {
const isPageInEditMode = useAtomComponentStateValue(
contextStoreIsPageInEditModeComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const { actionMenuType } = useContext(ActionMenuContext);

View file

@ -1,13 +1,11 @@
import { CommandMenuActionMenuDropdown } from '@/action-menu/components/CommandMenuActionMenuDropdown';
import { ActionMenuContextProvider } from '@/action-menu/contexts/ActionMenuContextProvider';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
export const RecordShowRightDrawerActionMenu = () => {
const contextStoreCurrentObjectMetadataItemId = useAtomComponentStateValue(
contextStoreCurrentObjectMetadataItemIdComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
return (

View file

@ -1,7 +1,6 @@
import { type ActionMenuContextType } from '@/action-menu/contexts/ActionMenuContext';
import { ActionMenuContextProviderDefault } from '@/action-menu/contexts/ActionMenuContextProviderDefault';
import { ActionMenuContextProviderWorkflowObjects } from '@/action-menu/contexts/ActionMenuContextProviderWorkflowObjects';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
@ -22,7 +21,6 @@ export const ActionMenuContextProvider = ({
}) => {
const contextStoreCurrentObjectMetadataItemId = useAtomComponentStateValue(
contextStoreCurrentObjectMetadataItemIdComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const objectMetadataItems = useAtomStateValue(objectMetadataItemsState);

View file

@ -7,7 +7,6 @@ import {
import { useRegisteredActions } from '@/action-menu/hooks/useRegisteredActions';
import { useShouldActionBeRegisteredParams } from '@/action-menu/hooks/useShouldActionBeRegisteredParams';
import { useCommandMenuItemFrontComponentActions } from '@/command-menu-item/hooks/useCommandMenuItemFrontComponentActions';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
@ -37,7 +36,6 @@ export const ActionMenuContextProviderDefault = ({
const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
contextStoreTargetedRecordsRuleComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const isRecordSelection =

View file

@ -6,7 +6,6 @@ import {
import { useRegisteredActions } from '@/action-menu/hooks/useRegisteredActions';
import { useShouldActionBeRegisteredParams } from '@/action-menu/hooks/useShouldActionBeRegisteredParams';
import { useCommandMenuItemFrontComponentActions } from '@/command-menu-item/hooks/useCommandMenuItemFrontComponentActions';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
@ -125,7 +124,6 @@ export const ActionMenuContextProviderWorkflowObjects = ({
}: ActionMenuContextProviderWorkflowObjectsProps) => {
const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
contextStoreTargetedRecordsRuleComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const recordId =

View file

@ -4,11 +4,9 @@ import { ActionViewType } from '@/action-menu/actions/types/ActionViewType';
import { type ShouldBeRegisteredFunctionParams } from '@/action-menu/actions/types/ShouldBeRegisteredFunctionParams';
import { getActionConfig } from '@/action-menu/actions/utils/getActionConfig';
import { getActionViewType } from '@/action-menu/actions/utils/getActionViewType';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreIsPageInEditModeComponentState } from '@/context-store/states/contextStoreIsPageInEditModeComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { isDefined } from 'twenty-shared/utils';
@ -24,19 +22,14 @@ export const useRegisteredActions = (
const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
contextStoreTargetedRecordsRuleComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const contextStoreCurrentViewType = useAtomComponentStateValue(
contextStoreCurrentViewTypeComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const { recordIndexId } = useRecordIndexIdFromCurrentContextStore();
const isFullTabWidgetInEditMode = useAtomComponentStateValue(
contextStoreIsPageInEditModeComponentState,
recordIndexId,
);
const viewType = getActionViewType(

View file

@ -4,7 +4,6 @@ import { getActionViewType } from '@/action-menu/actions/utils/getActionViewType
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { objectPermissionsFamilySelector } from '@/auth/states/objectPermissionsFamilySelector';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
@ -41,7 +40,6 @@ export const useShouldActionBeRegisteredParams = ({
const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
contextStoreTargetedRecordsRuleComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const recordId =
@ -97,19 +95,15 @@ export const useShouldActionBeRegisteredParams = ({
);
const isShowPage =
useAtomComponentStateValue(
contextStoreCurrentViewTypeComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
) === ContextStoreViewType.ShowPage;
useAtomComponentStateValue(contextStoreCurrentViewTypeComponentState) ===
ContextStoreViewType.ShowPage;
const numberOfSelectedRecords = useAtomComponentStateValue(
contextStoreNumberOfSelectedRecordsComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const contextStoreCurrentViewType = useAtomComponentStateValue(
contextStoreCurrentViewTypeComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const viewType = getActionViewType(

View file

@ -1,7 +1,4 @@
import { renderHook } from '@testing-library/react';
import { type ReactNode } from 'react';
import { RecoilRoot } from 'recoil';
import { useActivities } from '@/activities/hooks/useActivities';
import { type Task } from '@/activities/types/Task';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
@ -40,10 +37,6 @@ const mockActivity = {
taskTargets: [],
} satisfies Task;
const Wrapper = ({ children }: { children: ReactNode }) => (
<RecoilRoot>{children}</RecoilRoot>
);
describe('useActivities', () => {
afterEach(() => {
jest.clearAllMocks();
@ -60,21 +53,16 @@ describe('useActivities', () => {
},
);
const { result } = renderHook(
() => {
const activities = useActivities({
objectNameSingular: CoreObjectNameSingular.Task,
targetableObjects: [
{ targetObjectNameSingular: 'company', id: '123' },
],
skip: false,
limit: 10,
activityTargetsOrderByVariables: [{}],
});
return activities;
},
{ wrapper: Wrapper },
);
const { result } = renderHook(() => {
const activities = useActivities({
objectNameSingular: CoreObjectNameSingular.Task,
targetableObjects: [{ targetObjectNameSingular: 'company', id: '123' }],
skip: false,
limit: 10,
activityTargetsOrderByVariables: [{}],
});
return activities;
});
expect(result.current.activities).toEqual([mockActivity]);
});

View file

@ -3,7 +3,6 @@ import { MockedProvider } from '@apollo/client/testing';
import { act, renderHook } from '@testing-library/react';
import { type ReactNode } from 'react';
import { Provider as JotaiProvider } from 'jotai';
import { RecoilRoot } from 'recoil';
import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
@ -115,17 +114,15 @@ const task = {
const Wrapper = ({ children }: { children: ReactNode }) => (
<JotaiProvider store={jotaiStore}>
<RecoilRoot>
<MockedProvider cache={cache}>
<JestObjectMetadataItemSetter>
<SnackBarComponentInstanceContext.Provider
value={{ instanceId: 'snack-bar-manager' }}
>
{children}
</SnackBarComponentInstanceContext.Provider>
</JestObjectMetadataItemSetter>
</MockedProvider>
</RecoilRoot>
<MockedProvider cache={cache}>
<JestObjectMetadataItemSetter>
<SnackBarComponentInstanceContext.Provider
value={{ instanceId: 'snack-bar-manager' }}
>
{children}
</SnackBarComponentInstanceContext.Provider>
</JestObjectMetadataItemSetter>
</MockedProvider>
</JotaiProvider>
);

View file

@ -1,5 +1,5 @@
import { usePrepareFindManyActivitiesQuery } from '@/activities/hooks/usePrepareFindManyActivitiesQuery';
import { objectShowPageTargetableObjectStateV2 } from '@/activities/timeline-activities/states/objectShowPageTargetableObjectStateV2';
import { objectShowPageTargetableObjectState } from '@/activities/timeline-activities/states/objectShowPageTargetableObjectState';
import { type CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { isDefined } from 'twenty-shared/utils';
@ -13,7 +13,7 @@ export const useRefreshShowPageFindManyActivitiesQueries = ({
activityObjectNameSingular: CoreObjectNameSingular;
}) => {
const objectShowPageTargetableObject = useAtomStateValue(
objectShowPageTargetableObjectStateV2,
objectShowPageTargetableObjectState,
);
const { prepareFindManyActivitiesQuery } = usePrepareFindManyActivitiesQuery({

View file

@ -2,7 +2,7 @@ import { useCreateActivityInDB } from '@/activities/hooks/useCreateActivityInDB'
import { useRefreshShowPageFindManyActivitiesQueries } from '@/activities/hooks/useRefreshShowPageFindManyActivitiesQueries';
import { isActivityInCreateModeState } from '@/activities/states/isActivityInCreateModeState';
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
import { objectShowPageTargetableObjectStateV2 } from '@/activities/timeline-activities/states/objectShowPageTargetableObjectStateV2';
import { objectShowPageTargetableObjectState } from '@/activities/timeline-activities/states/objectShowPageTargetableObjectState';
import { type Note } from '@/activities/types/Note';
import { type Task } from '@/activities/types/Task';
import { type CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
@ -31,7 +31,7 @@ export const useUpsertActivity = ({
);
const objectShowPageTargetableObject = useAtomStateValue(
objectShowPageTargetableObjectStateV2,
objectShowPageTargetableObjectState,
);
const { refreshShowPageFindManyActivitiesQueries } =

View file

@ -10,20 +10,15 @@ jest.mock('@/activities/hooks/useActivities', () => ({
})),
}));
jest.mock('recoil', () => {
const actualRecoil = jest.requireActual('recoil');
return {
...actualRecoil,
useRecoilState: jest.fn(() => {
const mockCurrentNotesQueryVariables = {
filter: {},
orderBy: 'mockOrderBy',
};
return [mockCurrentNotesQueryVariables, jest.fn()];
}),
atom: jest.fn(),
};
});
jest.mock('@/ui/utilities/state/jotai/hooks/useAtomState', () => ({
useAtomState: jest.fn(() => {
const mockCurrentNotesQueryVariables = {
filter: {},
orderBy: 'mockOrderBy',
};
return [mockCurrentNotesQueryVariables, jest.fn()];
}),
}));
describe('useNotes', () => {
it('should return notes, and loading as expected', () => {

View file

@ -1,7 +1,7 @@
import { useEffect, useMemo } from 'react';
import { useActivities } from '@/activities/hooks/useActivities';
import { currentNotesQueryVariablesStateV2 } from '@/activities/notes/states/currentNotesQueryVariablesStateV2';
import { currentNotesQueryVariablesState } from '@/activities/notes/states/currentNotesQueryVariablesState';
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline-activities/constants/FindManyTimelineActivitiesOrderBy';
import { type Note } from '@/activities/types/Note';
import { type RecordGqlOperationVariables } from 'twenty-shared/types';
@ -34,7 +34,7 @@ export const useNotes = (targetableObject: ActivityTargetableObject) => {
});
const [currentNotesQueryVariables, setCurrentNotesQueryVariables] =
useAtomState(currentNotesQueryVariablesStateV2);
useAtomState(currentNotesQueryVariablesState);
// TODO: fix useEffect, remove with better pattern
useEffect(() => {

View file

@ -2,8 +2,8 @@ import { type RecordGqlOperationVariables } from 'twenty-shared/types';
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const currentNotesQueryVariablesStateV2 =
export const currentNotesQueryVariablesState =
createAtomState<RecordGqlOperationVariables | null>({
key: 'currentNotesQueryVariablesStateV2',
key: 'currentNotesQueryVariablesState',
defaultValue: null,
});

View file

@ -1,6 +1,5 @@
import { renderHook } from '@testing-library/react';
import { type ReactNode } from 'react';
import { RecoilRoot } from 'recoil';
import { useActivities } from '@/activities/hooks/useActivities';
import { useTasks } from '@/activities/tasks/hooks/useTasks';
@ -45,13 +44,11 @@ jest.mock('@/activities/hooks/useActivities', () => ({
(useActivities as jest.Mock).mockImplementation(useActivitiesMock);
const Wrapper = ({ children }: { children: ReactNode }) => (
<RecoilRoot>
<ObjectFilterDropdownComponentInstanceContext.Provider
value={{ instanceId: 'entity-tasks-filter-instance' }}
>
{children}
</ObjectFilterDropdownComponentInstanceContext.Provider>
</RecoilRoot>
<ObjectFilterDropdownComponentInstanceContext.Provider
value={{ instanceId: 'entity-tasks-filter-instance' }}
>
{children}
</ObjectFilterDropdownComponentInstanceContext.Provider>
);
describe('useTasks', () => {

View file

@ -2,8 +2,8 @@ import { type ActivityTargetableObject } from '@/activities/types/ActivityTarget
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const objectShowPageTargetableObjectStateV2 =
export const objectShowPageTargetableObjectState =
createAtomState<ActivityTargetableObject | null>({
key: 'objectShowPageTargetableObjectStateV2',
key: 'objectShowPageTargetableObjectState',
defaultValue: null,
});

View file

@ -1,5 +1,5 @@
import { agentChatUsageStateV2 } from '@/ai/states/agentChatUsageStateV2';
import { currentAIChatThreadStateV2 } from '@/ai/states/currentAIChatThreadStateV2';
import { agentChatUsageState } from '@/ai/states/agentChatUsageState';
import { currentAIChatThreadState } from '@/ai/states/currentAIChatThreadState';
import { currentAIChatThreadTitleState } from '@/ai/states/currentAIChatThreadTitleState';
import { useOpenAskAIPageInCommandMenu } from '@/command-menu/hooks/useOpenAskAIPageInCommandMenu';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
@ -79,11 +79,11 @@ export const AIChatThreadGroup = ({
}) => {
const { t } = useLingui();
const theme = useTheme();
const [, setCurrentAIChatThread] = useAtomState(currentAIChatThreadStateV2);
const [, setCurrentAIChatThread] = useAtomState(currentAIChatThreadState);
const setCurrentAIChatThreadTitle = useSetAtomState(
currentAIChatThreadTitleState,
);
const setAgentChatUsage = useSetAtomState(agentChatUsageStateV2);
const setAgentChatUsage = useSetAtomState(agentChatUsageState);
const { openAskAIPage } = useOpenAskAIPageInCommandMenu();
const handleThreadClick = (thread: AgentChatThread) => {

View file

@ -16,9 +16,9 @@ jest.mock('~/hooks/useCopyToClipboard', () => ({
}));
jest.mock(
'@/ui/utilities/state/component-state/hooks/useRecoilComponentValue',
'@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue',
() => ({
useRecoilComponentValue: () => 'output',
useAtomComponentSelectorValue: () => 'output',
}),
);

View file

@ -10,9 +10,9 @@ import { ProgressBar } from 'twenty-ui/feedback';
import { ContextUsageProgressRing } from '@/ai/components/internal/ContextUsageProgressRing';
import { SettingsBillingLabelValueItem } from '@/billing/components/internal/SettingsBillingLabelValueItem';
import {
agentChatUsageStateV2,
agentChatUsageState,
type AgentChatLastMessageUsage,
} from '@/ai/states/agentChatUsageStateV2';
} from '@/ai/states/agentChatUsageState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
const StyledContainer = styled.div`
@ -114,7 +114,7 @@ export const AIChatContextUsageButton = () => {
const { t } = useLingui();
const theme = useTheme();
const [isHovered, setIsHovered] = useState(false);
const agentChatUsage = useAtomStateValue(agentChatUsageStateV2);
const agentChatUsage = useAtomStateValue(agentChatUsageState);
if (!agentChatUsage) {
return (

View file

@ -1,5 +1,5 @@
import { agentChatSelectedFilesStateV2 } from '@/ai/states/agentChatSelectedFilesStateV2';
import { agentChatUploadedFilesStateV2 } from '@/ai/states/agentChatUploadedFilesStateV2';
import { agentChatSelectedFilesState } from '@/ai/states/agentChatSelectedFilesState';
import { agentChatUploadedFilesState } from '@/ai/states/agentChatUploadedFilesState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import styled from '@emotion/styled';
import { AgentChatFilePreview } from './AgentChatFilePreview';
@ -20,10 +20,10 @@ const StyledPreviewsContainer = styled.div`
export const AgentChatContextPreview = () => {
const [agentChatSelectedFiles, setAgentChatSelectedFiles] = useAtomState(
agentChatSelectedFilesStateV2,
agentChatSelectedFilesState,
);
const [agentChatUploadedFiles, setAgentChatUploadedFiles] = useAtomState(
agentChatUploadedFilesStateV2,
agentChatUploadedFilesState,
);
const handleRemoveUploadedFile = (fileIndex: number) => {

View file

@ -1,5 +1,5 @@
import { useAIChatFileUpload } from '@/ai/hooks/useAIChatFileUpload';
import { agentChatSelectedFilesStateV2 } from '@/ai/states/agentChatSelectedFilesStateV2';
import { agentChatSelectedFilesState } from '@/ai/states/agentChatSelectedFilesState';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
@ -19,7 +19,7 @@ const StyledFileInput = styled.input`
export const AgentChatFileUploadButton = () => {
const setAgentChatSelectedFiles = useSetAtomState(
agentChatSelectedFilesStateV2,
agentChatSelectedFilesState,
);
const fileInputRef = useRef<HTMLInputElement>(null);
const { uploadFiles } = useAIChatFileUpload();

View file

@ -1,5 +1,5 @@
import { useAgentChatContextOrThrow } from '@/ai/hooks/useAgentChatContextOrThrow';
import { agentChatInputStateV2 } from '@/ai/states/agentChatInputStateV2';
import { agentChatInputState } from '@/ai/states/agentChatInputState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { IconArrowUp, IconPlayerStop } from 'twenty-ui/display';
import { RoundedIconButton } from 'twenty-ui/input';
@ -9,7 +9,7 @@ type SendMessageButtonProps = {
};
export const SendMessageButton = ({ onSend }: SendMessageButtonProps) => {
const agentChatInput = useAtomStateValue(agentChatInputStateV2);
const agentChatInput = useAtomStateValue(agentChatInputState);
const { handleStop, isLoading, isStreaming } = useAgentChatContextOrThrow();
if (isStreaming) {

View file

@ -8,7 +8,7 @@ import {
DEFAULT_SUGGESTED_PROMPTS,
type SuggestedPrompt,
} from '@/ai/components/suggested-prompts/default-suggested-prompts';
import { agentChatInputStateV2 } from '@/ai/states/agentChatInputStateV2';
import { agentChatInputState } from '@/ai/states/agentChatInputState';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
const StyledContainer = styled.div`
@ -43,7 +43,7 @@ export const AIChatSuggestedPrompts = ({
editor,
}: AIChatSuggestedPromptsProps) => {
const { t: resolveMessage } = useLingui();
const setAgentChatInput = useSetAtomState(agentChatInputStateV2);
const setAgentChatInput = useSetAtomState(agentChatInputState);
const handleClick = (prompt: SuggestedPrompt) => {
const picked = pickRandom(prompt.prefillPrompts);

View file

@ -9,7 +9,7 @@ import { useCallback, useMemo } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { AI_CHAT_INPUT_ID } from '@/ai/constants/AiChatInputId';
import { agentChatInputStateV2 } from '@/ai/states/agentChatInputStateV2';
import { agentChatInputState } from '@/ai/states/agentChatInputState';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { MENTION_SUGGESTION_PLUGIN_KEY } from '@/mention/constants/MentionSuggestionPluginKey';
import { MentionSuggestion } from '@/mention/extensions/MentionSuggestion';
@ -25,7 +25,7 @@ type UseAIChatEditorProps = {
};
export const useAIChatEditor = ({ onSendMessage }: UseAIChatEditorProps) => {
const setAgentChatInput = useSetAtomState(agentChatInputStateV2);
const setAgentChatInput = useSetAtomState(agentChatInputState);
const { searchMentionRecords } = useMentionSearch();
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const { removeFocusItemFromFocusStackById } =

View file

@ -1,5 +1,5 @@
import { agentChatSelectedFilesStateV2 } from '@/ai/states/agentChatSelectedFilesStateV2';
import { agentChatUploadedFilesStateV2 } from '@/ai/states/agentChatUploadedFilesStateV2';
import { agentChatSelectedFilesState } from '@/ai/states/agentChatSelectedFilesState';
import { agentChatUploadedFilesState } from '@/ai/states/agentChatUploadedFilesState';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useApolloClient } from '@apollo/client';
@ -18,10 +18,10 @@ export const useAIChatFileUpload = () => {
const { t } = useLingui();
const { enqueueErrorSnackBar } = useSnackBar();
const [agentChatSelectedFiles, setAgentChatSelectedFiles] = useAtomState(
agentChatSelectedFilesStateV2,
agentChatSelectedFilesState,
);
const [agentChatUploadedFiles, setAgentChatUploadedFiles] = useAtomState(
agentChatUploadedFilesStateV2,
agentChatUploadedFilesState,
);
const sendFile = async (file: File): Promise<FileUIPart | null> => {

View file

@ -1,11 +1,11 @@
import { useGetBrowsingContext } from '@/ai/hooks/useBrowsingContext';
import { agentChatSelectedFilesStateV2 } from '@/ai/states/agentChatSelectedFilesStateV2';
import { agentChatUploadedFilesStateV2 } from '@/ai/states/agentChatUploadedFilesStateV2';
import { agentChatUsageStateV2 } from '@/ai/states/agentChatUsageStateV2';
import { currentAIChatThreadStateV2 } from '@/ai/states/currentAIChatThreadStateV2';
import { agentChatSelectedFilesState } from '@/ai/states/agentChatSelectedFilesState';
import { agentChatUploadedFilesState } from '@/ai/states/agentChatUploadedFilesState';
import { agentChatUsageState } from '@/ai/states/agentChatUsageState';
import { currentAIChatThreadState } from '@/ai/states/currentAIChatThreadState';
import { currentAIChatThreadTitleState } from '@/ai/states/currentAIChatThreadTitleState';
import { agentChatInputStateV2 } from '@/ai/states/agentChatInputStateV2';
import { agentChatInputState } from '@/ai/states/agentChatInputState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
@ -22,26 +22,22 @@ import { cookieStorage } from '~/utils/cookie-storage';
export const useAgentChat = (uiMessages: ExtendedUIMessage[]) => {
const setTokenPair = useSetAtomState(tokenPairState);
const setAgentChatUsage = useSetAtomState(agentChatUsageStateV2);
const setAgentChatUsage = useSetAtomState(agentChatUsageState);
const { getBrowsingContext } = useGetBrowsingContext();
const setCurrentAIChatThreadTitle = useSetAtomState(
currentAIChatThreadTitleState,
);
const agentChatSelectedFiles = useAtomStateValue(
agentChatSelectedFilesStateV2,
);
const agentChatSelectedFiles = useAtomStateValue(agentChatSelectedFilesState);
const currentAIChatThread = useAtomStateValue(currentAIChatThreadStateV2);
const currentAIChatThread = useAtomStateValue(currentAIChatThreadState);
const [agentChatUploadedFiles, setAgentChatUploadedFiles] = useAtomState(
agentChatUploadedFilesStateV2,
agentChatUploadedFilesState,
);
const [agentChatInput, setAgentChatInput] = useAtomState(
agentChatInputStateV2,
);
const [agentChatInput, setAgentChatInput] = useAtomState(agentChatInputState);
const retryFetchWithRenewedToken = async (
input: RequestInfo | URL,

View file

@ -1,15 +1,15 @@
import { useAgentChatScrollToBottom } from '@/ai/hooks/useAgentChatScrollToBottom';
import {
agentChatUsageStateV2,
agentChatUsageState,
type AgentChatUsageState,
} from '@/ai/states/agentChatUsageStateV2';
import { currentAIChatThreadStateV2 } from '@/ai/states/currentAIChatThreadStateV2';
} from '@/ai/states/agentChatUsageState';
import { currentAIChatThreadState } from '@/ai/states/currentAIChatThreadState';
import { currentAIChatThreadTitleState } from '@/ai/states/currentAIChatThreadTitleState';
import { isCreatingChatThreadStateV2 } from '@/ai/states/isCreatingChatThreadStateV2';
import { isCreatingChatThreadState } from '@/ai/states/isCreatingChatThreadState';
import { mapDBMessagesToUIMessages } from '@/ai/utils/mapDBMessagesToUIMessages';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { type SetterOrUpdater } from 'recoil';
import { type SetStateAction } from 'jotai';
import { isDefined } from 'twenty-shared/utils';
import {
type AgentChatThread,
@ -20,7 +20,9 @@ import {
const setUsageFromThread = (
thread: AgentChatThread,
setAgentChatUsage: SetterOrUpdater<AgentChatUsageState | null>,
setAgentChatUsage: (
update: SetStateAction<AgentChatUsageState | null>,
) => void,
) => {
const hasUsageData =
(thread.conversationSize ?? 0) > 0 && isDefined(thread.contextWindowTokens);
@ -42,14 +44,14 @@ const setUsageFromThread = (
export const useAgentChatData = () => {
const [currentAIChatThread, setCurrentAIChatThread] = useAtomState(
currentAIChatThreadStateV2,
currentAIChatThreadState,
);
const setAgentChatUsage = useSetAtomState(agentChatUsageStateV2);
const setAgentChatUsage = useSetAtomState(agentChatUsageState);
const setCurrentAIChatThreadTitle = useSetAtomState(
currentAIChatThreadTitleState,
);
const [isCreatingChatThread, setIsCreatingChatThread] = useAtomState(
isCreatingChatThreadStateV2,
isCreatingChatThreadState,
);
const { scrollToBottom } = useAgentChatScrollToBottom();

View file

@ -11,7 +11,7 @@ import { contextStoreFiltersComponentState } from '@/context-store/states/contex
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { recordStoreFamilySelectorV2 } from '@/object-record/record-store/states/selectors/recordStoreFamilySelectorV2';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useStore } from 'jotai';
@ -66,7 +66,7 @@ export const useGetBrowsingContext = () => {
};
const pageLayoutId = store.get(
recordStoreFamilySelectorV2.selectorFamily({
recordStoreFamilySelector.selectorFamily({
recordId: targetedRecordsRule.selectedRecordIds[0],
fieldName: 'pageLayoutId',
}),

View file

@ -1,5 +1,5 @@
import { agentChatUsageStateV2 } from '@/ai/states/agentChatUsageStateV2';
import { currentAIChatThreadStateV2 } from '@/ai/states/currentAIChatThreadStateV2';
import { agentChatUsageState } from '@/ai/states/agentChatUsageState';
import { currentAIChatThreadState } from '@/ai/states/currentAIChatThreadState';
import { currentAIChatThreadTitleState } from '@/ai/states/currentAIChatThreadTitleState';
import { useOpenAskAIPageInCommandMenu } from '@/command-menu/hooks/useOpenAskAIPageInCommandMenu';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
@ -7,8 +7,8 @@ import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomStat
import { useCreateChatThreadMutation } from '~/generated-metadata/graphql';
export const useCreateNewAIChatThread = () => {
const [, setCurrentAIChatThread] = useAtomState(currentAIChatThreadStateV2);
const setAgentChatUsage = useSetAtomState(agentChatUsageStateV2);
const [, setCurrentAIChatThread] = useAtomState(currentAIChatThreadState);
const setAgentChatUsage = useSetAtomState(agentChatUsageState);
const setCurrentAIChatThreadTitle = useSetAtomState(
currentAIChatThreadTitleState,
);

View file

@ -1,6 +1,6 @@
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const agentChatInputStateV2 = createAtomState<string>({
key: 'agentChatInputStateV2',
export const agentChatInputState = createAtomState<string>({
key: 'agentChatInputState',
defaultValue: '',
});

View file

@ -0,0 +1,6 @@
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const agentChatSelectedFilesState = createAtomState<File[]>({
key: 'ai/agentChatSelectedFilesState',
defaultValue: [],
});

View file

@ -1,6 +0,0 @@
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const agentChatSelectedFilesStateV2 = createAtomState<File[]>({
key: 'ai/agentChatSelectedFilesStateV2',
defaultValue: [],
});

View file

@ -2,7 +2,7 @@ import { type FileUIPart } from 'ai';
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const agentChatUploadedFilesStateV2 = createAtomState<FileUIPart[]>({
key: 'ai/agentChatUploadedFilesStateV2',
export const agentChatUploadedFilesState = createAtomState<FileUIPart[]>({
key: 'ai/agentChatUploadedFilesState',
defaultValue: [],
});

View file

@ -18,8 +18,7 @@ export type AgentChatUsageState = {
outputCredits: number;
};
export const agentChatUsageStateV2 =
createAtomState<AgentChatUsageState | null>({
key: 'agentChatUsageStateV2',
defaultValue: null,
});
export const agentChatUsageState = createAtomState<AgentChatUsageState | null>({
key: 'agentChatUsageState',
defaultValue: null,
});

View file

@ -0,0 +1,6 @@
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const currentAIChatThreadState = createAtomState<string | null>({
key: 'ai/currentAIChatThreadState',
defaultValue: null,
});

View file

@ -1,6 +0,0 @@
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const currentAIChatThreadStateV2 = createAtomState<string | null>({
key: 'ai/currentAIChatThreadStateV2',
defaultValue: null,
});

View file

@ -1,6 +1,6 @@
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const isCreatingChatThreadStateV2 = createAtomState<boolean>({
key: 'ai/isCreatingChatThreadStateV2',
export const isCreatingChatThreadState = createAtomState<boolean>({
key: 'ai/isCreatingChatThreadState',
defaultValue: false,
});

View file

@ -2,8 +2,6 @@ import { gql } from '@apollo/client';
import { MockedProvider, type MockedResponse } from '@apollo/client/testing';
import { act, renderHook, waitFor } from '@testing-library/react';
import { type ReactNode } from 'react';
import { RecoilRoot } from 'recoil';
import {
ANALYTICS_COOKIE_NAME,
useEventTracker,
@ -96,11 +94,9 @@ const mocks: MockedResponse[] = [
];
const Wrapper = ({ children }: { children: ReactNode }) => (
<RecoilRoot>
<MockedProvider mocks={mocks} addTypename={false}>
{children}
</MockedProvider>
</RecoilRoot>
<MockedProvider mocks={mocks} addTypename={false}>
{children}
</MockedProvider>
);
describe('useEventTracker', () => {

View file

@ -2,8 +2,6 @@ import { ApolloError, gql } from '@apollo/client';
import { act, renderHook } from '@testing-library/react';
import fetchMock, { enableFetchMocks } from 'jest-fetch-mock';
import { MemoryRouter, useLocation } from 'react-router-dom';
import { RecoilRoot } from 'recoil';
import { SnackBarComponentInstanceContext } from '@/ui/feedback/snack-bar-manager/contexts/SnackBarComponentInstanceContext';
import { useApolloFactory } from '@/apollo/hooks/useApolloFactory';
@ -21,18 +19,16 @@ jest.mock('react-router-dom', () => {
});
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RecoilRoot>
<MemoryRouter
initialEntries={['/welcome', '/verify', '/opportunities']}
initialIndex={2}
<MemoryRouter
initialEntries={['/welcome', '/verify', '/opportunities']}
initialIndex={2}
>
<SnackBarComponentInstanceContext.Provider
value={{ instanceId: 'test-instance-id' }}
>
<SnackBarComponentInstanceContext.Provider
value={{ instanceId: 'test-instance-id' }}
>
{children}
</SnackBarComponentInstanceContext.Provider>
</MemoryRouter>
</RecoilRoot>
{children}
</SnackBarComponentInstanceContext.Provider>
</MemoryRouter>
);
describe('useApolloFactory', () => {

View file

@ -1,6 +1,5 @@
import { AppRouter } from '@/app/components/AppRouter';
import { ApolloDevLogEffect } from '@/debug/components/ApolloDevLogEffect';
import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver';
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
import { AppRootErrorFallback } from '@/error-handler/components/AppRootErrorFallback';
import { ExceptionHandlerProvider } from '@/error-handler/components/ExceptionHandlerProvider';
@ -11,7 +10,6 @@ import { I18nProvider } from '@lingui/react';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { Provider as JotaiProvider } from 'jotai';
import { HelmetProvider } from 'react-helmet-async';
import { RecoilRoot } from 'recoil';
import { IconsProvider } from 'twenty-ui/display';
import { initialI18nActivate } from '~/utils/i18n/initialI18nActivate';
@ -20,32 +18,29 @@ initialI18nActivate();
export const App = () => {
return (
<JotaiProvider store={jotaiStore}>
<RecoilRoot>
<AppErrorBoundary
resetOnLocationChange={false}
FallbackComponent={AppRootErrorFallback}
>
<I18nProvider i18n={i18n}>
<RecoilDebugObserverEffect />
<ApolloDevLogEffect />
<SnackBarComponentInstanceContext.Provider
value={{ instanceId: 'snack-bar-manager' }}
>
<IconsProvider>
<ExceptionHandlerProvider>
<HelmetProvider>
<ClickOutsideListenerContext.Provider
value={{ excludedClickOutsideId: undefined }}
>
<AppRouter />
</ClickOutsideListenerContext.Provider>
</HelmetProvider>
</ExceptionHandlerProvider>
</IconsProvider>
</SnackBarComponentInstanceContext.Provider>
</I18nProvider>
</AppErrorBoundary>
</RecoilRoot>
<AppErrorBoundary
resetOnLocationChange={false}
FallbackComponent={AppRootErrorFallback}
>
<I18nProvider i18n={i18n}>
<ApolloDevLogEffect />
<SnackBarComponentInstanceContext.Provider
value={{ instanceId: 'snack-bar-manager' }}
>
<IconsProvider>
<ExceptionHandlerProvider>
<HelmetProvider>
<ClickOutsideListenerContext.Provider
value={{ excludedClickOutsideId: undefined }}
>
<AppRouter />
</ClickOutsideListenerContext.Provider>
</HelmetProvider>
</ExceptionHandlerProvider>
</IconsProvider>
</SnackBarComponentInstanceContext.Provider>
</I18nProvider>
</AppErrorBoundary>
</JotaiProvider>
);
};

View file

@ -1,7 +1,7 @@
import { GoToHotkeyItemEffect } from '@/app/effect-components/GoToHotkeyItemEffect';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { navigationDrawerExpandedMemorizedStateV2 } from '@/ui/navigation/states/navigationDrawerExpandedMemorizedStateV2';
import { navigationDrawerExpandedMemorizedState } from '@/ui/navigation/states/navigationDrawerExpandedMemorizedState';
import { useGoToHotkeys } from '@/ui/utilities/hotkey/hooks/useGoToHotkeys';
import { useStore } from 'jotai';
import { useCallback } from 'react';
@ -19,7 +19,7 @@ export const GotoHotkeysEffectsProvider = () => {
location: getSettingsPath(SettingsPath.ProfilePage),
preNavigateFunction: useCallback(() => {
store.set(isNavigationDrawerExpandedState.atom, true);
store.set(navigationDrawerExpandedMemorizedStateV2.atom, true);
store.set(navigationDrawerExpandedMemorizedState.atom, true);
}, [store]),
});

View file

@ -6,8 +6,6 @@ import { BILLING_CHECKOUT_SESSION_DEFAULT_VALUE } from '@/billing/constants/Bill
import deepEqual from 'deep-equal';
import { useStore } from 'jotai';
// Initialize state that are hydrated from query parameters
// We used to use recoil-sync to do this, but it was causing issues with Firefox
export const useInitializeQueryParamState = () => {
const store = useStore();
const initializeQueryParamState = useCallback(() => {

View file

@ -1,18 +1,14 @@
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { Logo } from '@/auth/components/Logo';
import {
ComponentDecorator,
RecoilRootDecorator,
RouterDecorator,
} from 'twenty-ui/testing';
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
const logoUrl = 'https://picsum.photos/192/192';
const meta: Meta<typeof Logo> = {
title: 'Modules/Auth/Logo',
component: Logo,
decorators: [ComponentDecorator, RecoilRootDecorator, RouterDecorator],
decorators: [ComponentDecorator, RouterDecorator],
};
export default meta;

View file

@ -1,7 +1,6 @@
import { type VerifyEmailEffect } from '@/auth/components/VerifyEmailEffect';
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import { RecoilRoot } from 'recoil';
// Mock component that just renders the error state of VerifyEmailEffect directly
// (since normal VerifyEmailEffect has async logic that's hard to test in Storybook)
@ -23,9 +22,7 @@ const meta: Meta<typeof VerifyEmailEffectErrorState> = {
decorators: [
(Story) => (
<div style={{ padding: '24px' }}>
<RecoilRoot>
<Story />
</RecoilRoot>
<Story />
</div>
),
SnackBarDecorator,
@ -48,20 +45,18 @@ export const ErrorState: Story = {
export const IntegratedExample: StoryObj<typeof VerifyEmailEffect> = {
render: () => (
<RecoilRoot>
<MemoryRouter
initialEntries={[
'/verify-email?email=user@example.com&emailVerificationToken=invalid-token',
]}
>
<Routes>
<Route
path="/verify-email"
element={<VerifyEmailEffectErrorState email="user@example.com" />}
/>
</Routes>
</MemoryRouter>
</RecoilRoot>
<MemoryRouter
initialEntries={[
'/verify-email?email=user@example.com&emailVerificationToken=invalid-token',
]}
>
<Routes>
<Route
path="/verify-email"
element={<VerifyEmailEffectErrorState email="user@example.com" />}
/>
</Routes>
</MemoryRouter>
),
parameters: {
docs: {

View file

@ -9,7 +9,6 @@ import { useApolloClient } from '@apollo/client';
import { MockedProvider } from '@apollo/client/testing';
import { type ReactNode, act } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { RecoilRoot } from 'recoil';
import {
email,
@ -75,15 +74,13 @@ jest.mock('@/domain-manager/hooks/useLastAuthenticatedWorkspaceDomain', () => ({
const Wrapper = ({ children }: { children: ReactNode }) => (
<MockedProvider mocks={Object.values(mocks)} addTypename={false}>
<RecoilRoot>
<MemoryRouter>
<SnackBarComponentInstanceContext.Provider
value={{ instanceId: 'test-instance-id' }}
>
{children}
</SnackBarComponentInstanceContext.Provider>
</MemoryRouter>
</RecoilRoot>
<MemoryRouter>
<SnackBarComponentInstanceContext.Provider
value={{ instanceId: 'test-instance-id' }}
>
{children}
</SnackBarComponentInstanceContext.Provider>
</MemoryRouter>
</MockedProvider>
);

View file

@ -1,25 +1,19 @@
import { act, renderHook } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { tokenPairState } from '@/auth/states/tokenPairState';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
const renderHooks = () => {
const { result } = renderHook(
() => {
const isLogged = useIsLogged();
const setTokenPair = useSetAtomState(tokenPairState);
const { result } = renderHook(() => {
const isLogged = useIsLogged();
const setTokenPair = useSetAtomState(tokenPairState);
return {
isLogged,
setTokenPair,
};
},
{
wrapper: RecoilRoot,
},
);
return {
isLogged,
setTokenPair,
};
});
return { result };
};

View file

@ -1,7 +1,6 @@
import { i18n } from '@lingui/core';
import { I18nProvider } from '@lingui/react';
import { renderHook } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { AppPath } from 'twenty-shared/types';
@ -28,8 +27,7 @@ dynamicActivate(SOURCE_LOCALE);
const renderHooks = () => {
const { result } = renderHook(() => useVerifyLogin(), {
wrapper: ({ children }) =>
RecoilRoot({ children: I18nProvider({ i18n, children }) }),
wrapper: ({ children }) => I18nProvider({ i18n, children }),
});
return { result };
};

View file

@ -1,6 +1,5 @@
import { ApolloError, useApolloClient } from '@apollo/client';
import { useCallback } from 'react';
import { snapshot_UNSTABLE, useGotoRecoilSnapshot } from 'recoil';
import { AppPath } from 'twenty-shared/types';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
@ -113,8 +112,6 @@ export const useAuth = () => {
const client = useApolloClient();
const goToRecoilSnapshot = useGotoRecoilSnapshot();
const [, setSearchParams] = useSearchParams();
const navigate = useNavigate();
@ -124,8 +121,6 @@ export const useAuth = () => {
sseClient?.dispose();
const emptySnapshot = snapshot_UNSTABLE();
const authProvidersValue = store.get(workspaceAuthProvidersState.atom);
const domainConfigurationValue = store.get(domainConfigurationState.atom);
const workspacePublicDataValue = store.get(workspacePublicDataState.atom);
@ -136,23 +131,15 @@ export const useAuth = () => {
isCaptchaScriptLoadedState.atom,
);
const initialSnapshot = emptySnapshot.map(() => {
return undefined;
});
sessionStorage.clear();
localStorage.clear();
goToRecoilSnapshot(initialSnapshot);
store.set(workspaceAuthProvidersState.atom, authProvidersValue);
store.set(workspacePublicDataState.atom, workspacePublicDataValue);
store.set(domainConfigurationState.atom, domainConfigurationValue);
store.set(isCaptchaScriptLoadedState.atom, isCaptchaScriptLoadedValue);
store.set(lastAuthenticatedMethodState.atom, lastAuthenticatedMethod);
// Reset user-data Jotai states that were migrated from Recoil
// (Recoil snapshot reset no longer handles these since they are Jotai V2)
store.set(tokenPairState.atom, null);
store.set(currentUserState.atom, null);
store.set(currentWorkspaceState.atom, null);
@ -172,7 +159,6 @@ export const useAuth = () => {
await resetToMockedMetadata();
navigate(AppPath.SignInUp);
}, [
goToRecoilSnapshot,
client,
setLastAuthenticateWorkspaceDomain,
resetToMockedMetadata,

View file

@ -3,7 +3,6 @@ import { I18nProvider } from '@lingui/react';
import { act, renderHook } from '@testing-library/react';
import { type ReactNode, createElement } from 'react';
import { Provider as JotaiProvider } from 'jotai';
import { RecoilRoot } from 'recoil';
import { useHandleResetPassword } from '@/auth/sign-in-up/hooks/useHandleResetPassword';
import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState';
@ -32,11 +31,7 @@ const renderHooks = () => {
createElement(
JotaiProvider,
{ store: jotaiStore },
createElement(
RecoilRoot,
null as any,
createElement(I18nProvider, { i18n }, children),
),
createElement(I18nProvider, { i18n }, children),
),
});
return { result };

View file

@ -8,7 +8,6 @@ import { renderHook } from '@testing-library/react';
import { type ReactNode } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { Provider as JotaiProvider } from 'jotai';
import { RecoilRoot } from 'recoil';
const TestWrapper = ({
children,
@ -25,9 +24,7 @@ const TestWrapper = ({
);
return (
<MemoryRouter initialEntries={[initialEntry]}>
<JotaiProvider store={jotaiStore}>
<RecoilRoot>{children}</RecoilRoot>
</JotaiProvider>
<JotaiProvider store={jotaiStore}>{children}</JotaiProvider>
</MemoryRouter>
);
};

View file

@ -1,16 +1,12 @@
import { act, renderHook } from '@testing-library/react';
import { type ReactNode } from 'react';
import { Provider as JotaiProvider } from 'jotai';
import { RecoilRoot } from 'recoil';
import { captchaTokenState } from '@/captcha/states/captchaTokenState';
import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
const Wrapper = ({ children }: { children: ReactNode }) => (
<JotaiProvider store={jotaiStore}>
<RecoilRoot>{children}</RecoilRoot>
</JotaiProvider>
<JotaiProvider store={jotaiStore}>{children}</JotaiProvider>
);
describe('useReadCaptchaToken', () => {

View file

@ -1,6 +1,5 @@
import { act, renderHook, waitFor } from '@testing-library/react';
import { type ReactNode } from 'react';
import { RecoilRoot } from 'recoil';
import { Provider as JotaiProvider } from 'jotai';
import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken';
@ -13,9 +12,7 @@ import { type Captcha, CaptchaDriverType } from '~/generated-metadata/graphql';
jest.mock('@/captcha/utils/isCaptchaRequiredForPath');
const createWrapper = ({ children }: { children: ReactNode }) => (
<JotaiProvider store={jotaiStore}>
<RecoilRoot>{children}</RecoilRoot>
</JotaiProvider>
<JotaiProvider store={jotaiStore}>{children}</JotaiProvider>
);
describe('useRequestFreshCaptchaToken', () => {

View file

@ -21,19 +21,17 @@ import { isImapSmtpCaldavEnabledState } from '@/client-config/states/isImapSmtpC
import { isMicrosoftCalendarEnabledState } from '@/client-config/states/isMicrosoftCalendarEnabledState';
import { isMicrosoftMessagingEnabledState } from '@/client-config/states/isMicrosoftMessagingEnabledState';
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
import { labPublicFeatureFlagsStateV2 } from '@/client-config/states/labPublicFeatureFlagsStateV2';
import { labPublicFeatureFlagsState } from '@/client-config/states/labPublicFeatureFlagsState';
import { sentryConfigState } from '@/client-config/states/sentryConfigState';
import { supportChatState } from '@/client-config/states/supportChatState';
import { type ClientConfig } from '@/client-config/types/ClientConfig';
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
import { useCallback } from 'react';
import { isAttachmentPreviewEnabledStateV2 } from '@/client-config/states/isAttachmentPreviewEnabledStateV2';
import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
import { getClientConfig } from '@/client-config/utils/getClientConfig';
import { allowRequestsToTwentyIconsState } from '@/client-config/states/allowRequestsToTwentyIcons';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useStore } from 'jotai';
type UseClientConfigResult = {
data: { clientConfig: ClientConfig } | undefined;
@ -44,7 +42,6 @@ type UseClientConfigResult = {
};
export const useClientConfig = (): UseClientConfigResult => {
const store = useStore();
const setIsAnalyticsEnabled = useSetAtomState(isAnalyticsEnabledState);
const setDomainConfiguration = useSetAtomState(domainConfigurationState);
const setAuthProviders = useSetAtomState(authProvidersState);
@ -76,9 +73,7 @@ export const useClientConfig = (): UseClientConfigResult => {
const setCanManageFeatureFlags = useSetAtomState(canManageFeatureFlagsState);
const setLabPublicFeatureFlags = useSetAtomState(
labPublicFeatureFlagsStateV2,
);
const setLabPublicFeatureFlags = useSetAtomState(labPublicFeatureFlagsState);
const setMicrosoftMessagingEnabled = useSetAtomState(
isMicrosoftMessagingEnabledState,
@ -188,10 +183,6 @@ export const useClientConfig = (): UseClientConfigResult => {
setGoogleMessagingEnabled(clientConfig?.isGoogleMessagingEnabled);
setGoogleCalendarEnabled(clientConfig?.isGoogleCalendarEnabled);
setIsAttachmentPreviewEnabled(clientConfig?.isAttachmentPreviewEnabled);
store.set(
isAttachmentPreviewEnabledStateV2.atom,
clientConfig?.isAttachmentPreviewEnabled,
);
setIsConfigVariablesInDbEnabled(
clientConfig?.isConfigVariablesInDbEnabled,
);
@ -249,7 +240,6 @@ export const useClientConfig = (): UseClientConfigResult => {
setSentryConfig,
setSupportChat,
setAllowRequestsToTwentyIcons,
store,
]);
return {

View file

@ -1,5 +1,6 @@
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const isAttachmentPreviewEnabledState = createAtomState<boolean>({
key: 'isAttachmentPreviewEnabled',
key: 'isAttachmentPreviewEnabledState',
defaultValue: false,
});

View file

@ -1,6 +0,0 @@
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const isAttachmentPreviewEnabledStateV2 = createAtomState<boolean>({
key: 'isAttachmentPreviewEnabledStateV2',
defaultValue: false,
});

View file

@ -2,9 +2,7 @@ import { type PublicFeatureFlag } from '~/generated-metadata/graphql';
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const labPublicFeatureFlagsStateV2 = createAtomState<
PublicFeatureFlag[]
>({
key: 'labPublicFeatureFlagsStateV2',
export const labPublicFeatureFlagsState = createAtomState<PublicFeatureFlag[]>({
key: 'labPublicFeatureFlagsState',
defaultValue: [],
});

View file

@ -11,7 +11,7 @@ import { FOLDER_ICON_DEFAULT } from '@/navigation-menu-item/constants/FolderIcon
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { useUpdateFolderInDraft } from '@/navigation-menu-item/hooks/useUpdateFolderInDraft';
import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
import { selectedNavigationMenuItemInEditModeStateV2 } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeStateV2';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
import { IconPicker } from '@/ui/input/components/IconPicker';
@ -33,7 +33,7 @@ export const CommandMenuFolderInfo = () => {
commandMenuPageInfo.instanceId,
);
const selectedNavigationMenuItemInEditMode = useAtomStateValue(
selectedNavigationMenuItemInEditModeStateV2,
selectedNavigationMenuItemInEditModeState,
);
const items = useWorkspaceSectionItems();
const { updateFolderInDraft } = useUpdateFolderInDraft();

View file

@ -1,6 +1,6 @@
import { CommandMenuOpenContainer } from '@/command-menu/components/CommandMenuOpenContainer';
import { CommandMenuRouter } from '@/command-menu/components/CommandMenuRouter';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import styled from '@emotion/styled';
@ -13,7 +13,7 @@ const StyledCommandMenuMobileFullScreenContainer = styled.div`
`;
export const CommandMenuForMobile = () => {
const isCommandMenuOpened = useAtomStateValue(isCommandMenuOpenedStateV2);
const isCommandMenuOpened = useAtomStateValue(isCommandMenuOpenedState);
return (
<AnimatePresence>

View file

@ -6,7 +6,7 @@ import { type IconComponent } from 'twenty-ui/display';
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { AddToNavigationDragHandle } from '@/navigation-menu-item/components/AddToNavigationDragHandle';
import { addToNavPayloadRegistryStateV2 } from '@/navigation-menu-item/states/addToNavPayloadRegistryStateV2';
import { addToNavPayloadRegistryState } from '@/navigation-menu-item/states/addToNavPayloadRegistryState';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import type { AddToNavigationDragPayload } from '@/navigation-menu-item/types/add-to-navigation-drag-payload';
@ -41,7 +41,7 @@ export const CommandMenuItemWithAddToNavigationDrag = ({
dragIndex,
}: CommandMenuItemWithAddToNavigationDragProps) => {
const { t } = useLingui();
const setRegistry = useSetAtomState(addToNavPayloadRegistryStateV2);
const setRegistry = useSetAtomState(addToNavPayloadRegistryState);
const [isHovered, setIsHovered] = useState(false);
const contextualDescription = isHovered

View file

@ -9,7 +9,7 @@ import { NavigationMenuItemType } from '@/navigation-menu-item/constants/Navigat
import { StyledNavigationMenuItemIconContainer } from '@/navigation-menu-item/components/NavigationMenuItemIconContainer';
import { useUpdateLinkInDraft } from '@/navigation-menu-item/hooks/useUpdateLinkInDraft';
import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
import { selectedNavigationMenuItemInEditModeStateV2 } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeStateV2';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
import { TitleInput } from '@/ui/input/components/TitleInput';
@ -25,7 +25,7 @@ export const CommandMenuLinkInfo = () => {
commandMenuPageInfo.instanceId,
);
const selectedNavigationMenuItemInEditMode = useAtomStateValue(
selectedNavigationMenuItemInEditModeStateV2,
selectedNavigationMenuItemInEditModeState,
);
const items = useWorkspaceSectionItems();
const { updateLinkInDraft } = useUpdateLinkInDraft();

View file

@ -2,7 +2,7 @@ import { COMMAND_MENU_ANIMATION_VARIANTS } from '@/command-menu/constants/Comman
import { COMMAND_MENU_CLICK_OUTSIDE_ID } from '@/command-menu/constants/CommandMenuClickOutsideId';
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { isSidePanelAnimatingStateV2 } from '@/command-menu/states/isSidePanelAnimatingStateV2';
import { isSidePanelAnimatingState } from '@/command-menu/states/isSidePanelAnimatingState';
import { type CommandMenuAnimationVariant } from '@/command-menu/types/CommandMenuAnimationVariant';
import { RECORD_CHIP_CLICK_OUTSIDE_ID } from '@/object-record/record-table/constants/RecordChipClickOutsideId';
import { MENTION_MENU_DROPDOWN_CLICK_OUTSIDE_ID } from '@/ui/input/constants/MentionMenuDropdownClickOutsideId';
@ -54,7 +54,7 @@ export const CommandMenuOpenContainer = ({
const { closeCommandMenu } = useCommandMenu();
const commandMenuRef = useRef<HTMLDivElement>(null);
const setIsSidePanelAnimating = useSetAtomState(isSidePanelAnimatingStateV2);
const setIsSidePanelAnimating = useSetAtomState(isSidePanelAnimatingState);
const store = useStore();

View file

@ -12,7 +12,7 @@ import { CommandMenuRecordInfo } from '@/command-menu/components/CommandMenuReco
import { CommandMenuWorkflowStepInfo } from '@/command-menu/components/CommandMenuWorkflowStepInfo';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
import { selectedNavigationMenuItemInEditModeStateV2 } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeStateV2';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { CommandMenuPages } from 'twenty-shared/types';
@ -30,7 +30,7 @@ type CommandMenuPageInfoProps = {
export const CommandMenuPageInfo = ({ pageChip }: CommandMenuPageInfoProps) => {
const selectedNavigationMenuItemInEditMode = useAtomStateValue(
selectedNavigationMenuItemInEditModeStateV2,
selectedNavigationMenuItemInEditModeState,
);
const items = useWorkspaceSectionItems();

View file

@ -7,8 +7,8 @@ import { useIsRecordFieldReadOnly } from '@/object-record/read-only/hooks/useIsR
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
import { useRecordShowContainerActions } from '@/object-record/record-show/hooks/useRecordShowContainerActions';
import { useRecordShowPage } from '@/object-record/record-show/hooks/useRecordShowPage';
import { recordStoreFamilySelectorV2 } from '@/object-record/record-store/states/selectors/recordStoreFamilySelectorV2';
import { recordStoreIdentifierFamilySelectorV2 } from '@/object-record/record-store/states/selectors/recordStoreIdentifierFamilySelectorV2';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { recordStoreIdentifierFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreIdentifierFamilySelector';
import { RecordTitleCell } from '@/object-record/record-title-cell/components/RecordTitleCell';
import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/types/RecordTitleCellContainerType';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
@ -50,7 +50,7 @@ export const CommandMenuRecordInfo = ({
);
const recordCreatedAt = useAtomFamilySelectorValue(
recordStoreFamilySelectorV2,
recordStoreFamilySelector,
{
recordId: objectRecordId,
fieldName: 'createdAt',
@ -62,7 +62,7 @@ export const CommandMenuRecordInfo = ({
);
const recordIdentifier = useAtomFamilySelectorValue(
recordStoreIdentifierFamilySelectorV2,
recordStoreIdentifierFamilySelector,
{
recordId: objectRecordId,
allowRequestsToTwentyIcons,

View file

@ -7,7 +7,7 @@ import {
commandMenuWidthState,
} from '@/command-menu/states/commandMenuWidthState';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { tableWidthResizeIsActiveState } from '@/object-record/record-table/states/tableWidthResizeIsActivedState';
import { ModalContainerContext } from '@/ui/layout/modal/contexts/ModalContainerContext';
import { ResizablePanelGap } from '@/ui/layout/resizable-panel/components/ResizablePanelGap';
@ -56,7 +56,7 @@ const StyledModalContainer = styled.div`
const GAP_WIDTH = 8;
export const CommandMenuSidePanelForDesktop = () => {
const isCommandMenuOpened = useAtomStateValue(isCommandMenuOpenedStateV2);
const isCommandMenuOpened = useAtomStateValue(isCommandMenuOpenedState);
const isCommandMenuClosing = useAtomStateValue(isCommandMenuClosingState);
const [commandMenuWidth, setCommandMenuWidth] = useAtomState(
commandMenuWidthState,

View file

@ -27,7 +27,7 @@ import { CommandMenuRouter } from '@/command-menu/components/CommandMenuRouter';
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
@ -86,7 +86,7 @@ const meta: Meta<typeof CommandMenu> = {
currentUserWorkspaceState.atom,
mockedUserData.currentUserWorkspace,
);
jotaiStore.set(isCommandMenuOpenedStateV2.atom, true);
jotaiStore.set(isCommandMenuOpenedState.atom, true);
jotaiStore.set(commandMenuNavigationStackState.atom, [
{
page: CommandMenuPages.Root,

View file

@ -2,27 +2,24 @@ import { renderHook } from '@testing-library/react';
import { Provider as JotaiProvider } from 'jotai';
import { act } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { RecoilRoot } from 'recoil';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { CommandMenuPages } from 'twenty-shared/types';
import { IconDotsVertical } from 'twenty-ui/display';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<JotaiProvider store={jotaiStore}>
<RecoilRoot>
<MemoryRouter
initialEntries={['/one', '/two', { pathname: '/three' }]}
initialIndex={1}
>
{children}
</MemoryRouter>
</RecoilRoot>
<MemoryRouter
initialEntries={['/one', '/two', { pathname: '/three' }]}
initialIndex={1}
>
{children}
</MemoryRouter>
</JotaiProvider>
);
@ -54,31 +51,31 @@ describe('useCommandMenu', () => {
result.current.commandMenu.openCommandMenu();
});
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(true);
expect(jotaiStore.get(isCommandMenuOpenedState.atom)).toBe(true);
act(() => {
result.current.commandMenu.closeCommandMenu();
});
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(false);
expect(jotaiStore.get(isCommandMenuOpenedState.atom)).toBe(false);
});
it('should toggle the command menu', () => {
const { result } = renderHooks();
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(false);
expect(jotaiStore.get(isCommandMenuOpenedState.atom)).toBe(false);
act(() => {
result.current.commandMenu.toggleCommandMenu();
});
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(true);
expect(jotaiStore.get(isCommandMenuOpenedState.atom)).toBe(true);
act(() => {
result.current.commandMenu.toggleCommandMenu();
});
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(false);
expect(jotaiStore.get(isCommandMenuOpenedState.atom)).toBe(false);
});
it('should navigate command menu and reset navigation stack when resetNavigationStack is true', () => {

View file

@ -2,8 +2,6 @@ import { renderHook } from '@testing-library/react';
import { Provider as JotaiProvider } from 'jotai';
import { act } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { RecoilRoot } from 'recoil';
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID } from '@/command-menu/constants/CommandMenuContextChipGroupsDropdownId';
import { COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuPreviousComponentInstanceId';
@ -14,7 +12,7 @@ import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
@ -53,9 +51,7 @@ jest.mock('@/ui/layout/right-drawer/utils/emitSidePanelCloseEvent', () => ({
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<JotaiProvider store={jotaiStore}>
<RecoilRoot>
<MemoryRouter>{children}</MemoryRouter>
</RecoilRoot>
<MemoryRouter>{children}</MemoryRouter>
</JotaiProvider>
);
@ -97,7 +93,7 @@ describe('useCommandMenuCloseAnimationCompleteCleanup', () => {
Icon: IconList,
instanceId: 'test-id',
});
jotaiStore.set(isCommandMenuOpenedStateV2.atom, true);
jotaiStore.set(isCommandMenuOpenedState.atom, true);
jotaiStore.set(commandMenuSearchState.atom, 'test search');
jotaiStore.set(commandMenuNavigationStackState.atom, [
{
@ -120,7 +116,7 @@ describe('useCommandMenuCloseAnimationCompleteCleanup', () => {
Icon: IconList,
instanceId: 'test-id',
});
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(true);
expect(jotaiStore.get(isCommandMenuOpenedState.atom)).toBe(true);
expect(jotaiStore.get(commandMenuSearchState.atom)).toBe('test search');
expect(jotaiStore.get(commandMenuNavigationStackState.atom)).toEqual([
{
@ -146,7 +142,7 @@ describe('useCommandMenuCloseAnimationCompleteCleanup', () => {
Icon: undefined,
instanceId: '',
});
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(false);
expect(jotaiStore.get(isCommandMenuOpenedState.atom)).toBe(false);
expect(jotaiStore.get(commandMenuSearchState.atom)).toBe('');
expect(jotaiStore.get(commandMenuNavigationStackState.atom)).toEqual([]);
expect(jotaiStore.get(hasUserSelectedCommandState.atom)).toBe(false);

View file

@ -2,24 +2,20 @@ import { renderHook } from '@testing-library/react';
import { Provider as JotaiProvider } from 'jotai';
import { act } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { RecoilRoot } from 'recoil';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useCommandMenuCloseAnimationCompleteCleanup } from '@/command-menu/hooks/useCommandMenuCloseAnimationCompleteCleanup';
import { useCommandMenuHistory } from '@/command-menu/hooks/useCommandMenuHistory';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { CommandMenuPages } from 'twenty-shared/types';
import { IconList, IconSearch } from 'twenty-ui/display';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<JotaiProvider store={jotaiStore}>
<RecoilRoot>
<MemoryRouter>{children}</MemoryRouter>
</RecoilRoot>
<MemoryRouter>{children}</MemoryRouter>
</JotaiProvider>
);
@ -116,7 +112,7 @@ describe('useCommandMenuHistory', () => {
instanceId: '',
Icon: undefined,
});
expect(jotaiStore.get(isCommandMenuOpenedStateV2.atom)).toBe(false);
expect(jotaiStore.get(isCommandMenuOpenedState.atom)).toBe(false);
});
it('should navigate to a page in history', () => {

View file

@ -1,18 +1,15 @@
import { renderHook } from '@testing-library/react';
import { act } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { RecoilRoot } from 'recoil';
import { useCommandMenuOnItemClick } from '@/command-menu/hooks/useCommandMenuOnItemClick';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RecoilRoot>
<MemoryRouter
initialEntries={['/one', '/two', { pathname: '/three' }]}
initialIndex={1}
>
{children}
</MemoryRouter>
</RecoilRoot>
<MemoryRouter
initialEntries={['/one', '/two', { pathname: '/three' }]}
initialIndex={1}
>
{children}
</MemoryRouter>
);
const renderHooks = () => {

View file

@ -4,15 +4,11 @@ import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { renderHook } from '@testing-library/react';
import { Provider as JotaiProvider } from 'jotai';
import { act } from 'react';
import { RecoilRoot } from 'recoil';
const pageId = 'merge-page-id';
const objectMetadataId = 'company-metadata-id';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<JotaiProvider store={jotaiStore}>
<RecoilRoot>{children}</RecoilRoot>
</JotaiProvider>
<JotaiProvider store={jotaiStore}>{children}</JotaiProvider>
);
const renderHooks = (initialRecordIds: string[]) => {

View file

@ -1,10 +1,9 @@
import { act, renderHook } from '@testing-library/react';
import { Provider as JotaiProvider } from 'jotai';
import { type ReactNode } from 'react';
import { RecoilRoot } from 'recoil';
import { useOpenAskAIPageInCommandMenu } from '@/command-menu/hooks/useOpenAskAIPageInCommandMenu';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { CommandMenuPages } from 'twenty-shared/types';
import { IconSparkles } from 'twenty-ui/display';
@ -21,15 +20,13 @@ jest.mock('@/command-menu/hooks/useCommandMenu', () => ({
}));
const Wrapper = ({ children }: { children: ReactNode }) => (
<JotaiProvider store={jotaiStore}>
<RecoilRoot>{children}</RecoilRoot>
</JotaiProvider>
<JotaiProvider store={jotaiStore}>{children}</JotaiProvider>
);
describe('useOpenAskAIPageInCommandMenu', () => {
beforeEach(() => {
jest.clearAllMocks();
jotaiStore.set(isCommandMenuOpenedStateV2.atom, false);
jotaiStore.set(isCommandMenuOpenedState.atom, false);
});
it('should navigate to AskAI page with correct defaults', () => {
@ -51,7 +48,7 @@ describe('useOpenAskAIPageInCommandMenu', () => {
});
it('should use resetNavigationStack from argument when provided', () => {
jotaiStore.set(isCommandMenuOpenedStateV2.atom, true);
jotaiStore.set(isCommandMenuOpenedState.atom, true);
const { result } = renderHook(() => useOpenAskAIPageInCommandMenu(), {
wrapper: Wrapper,
@ -69,7 +66,7 @@ describe('useOpenAskAIPageInCommandMenu', () => {
});
it('should default resetNavigationStack to isCommandMenuOpened', () => {
jotaiStore.set(isCommandMenuOpenedStateV2.atom, true);
jotaiStore.set(isCommandMenuOpenedState.atom, true);
const { result } = renderHook(() => useOpenAskAIPageInCommandMenu(), {
wrapper: Wrapper,

View file

@ -20,22 +20,21 @@ import { getPeopleRecordConnectionMock } from '~/testing/mock-data/people';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
import { contextStoreFilterGroupsComponentState } from '@/context-store/states/contextStoreFilterGroupsComponentState';
const mockCopyContextStoreStates = jest.fn();
jest.mock(
'@/command-menu/hooks/useCopyContextStoreAndActionMenuStates',
() => ({
useCopyContextStoreStates: () => ({
copyContextStoreStates: mockCopyContextStoreStates,
}),
}),
);
const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
(item) => item.nameSingular === 'person',
)!;
const peopleMock = getPeopleRecordConnectionMock();
jotaiStore.set(
recordStoreFamilyState.atomFamily(peopleMock[0].id),
peopleMock[0],
);
jotaiStore.set(
recordStoreFamilyState.atomFamily(peopleMock[1].id),
peopleMock[1],
);
const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
apolloMocks: [],
componentInstanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
@ -48,16 +47,6 @@ const wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
},
contextStoreNumberOfSelectedRecords: 2,
contextStoreCurrentViewType: ContextStoreViewType.Table,
onInitializeRecoilSnapshot: (_snapshot) => {
jotaiStore.set(
recordStoreFamilyState.atomFamily(peopleMock[0].id),
peopleMock[0],
);
jotaiStore.set(
recordStoreFamilyState.atomFamily(peopleMock[1].id),
peopleMock[1],
);
},
});
describe('useSetGlobalCommandMenuContext', () => {
@ -161,18 +150,41 @@ describe('useSetGlobalCommandMenuContext', () => {
expect(hasUserSelectedCommandAfter).toBe(false);
});
it('should call copyContextStoreStates with correct parameters', () => {
const { result } = renderHook(() => useSetGlobalCommandMenuContext(), {
wrapper,
});
it('should copy context store states to previous instance before resetting', () => {
const { result } = renderHook(
() => {
const { setGlobalCommandMenuContext } =
useSetGlobalCommandMenuContext();
const previousTargetedRecordsRule = useAtomComponentStateValue(
contextStoreTargetedRecordsRuleComponentState,
COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID,
);
const previousNumberOfSelectedRecords = useAtomComponentStateValue(
contextStoreNumberOfSelectedRecordsComponentState,
COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID,
);
return {
setGlobalCommandMenuContext,
previousTargetedRecordsRule,
previousNumberOfSelectedRecords,
};
},
{
wrapper,
},
);
act(() => {
result.current.setGlobalCommandMenuContext();
});
expect(mockCopyContextStoreStates).toHaveBeenCalledWith({
instanceIdToCopyFrom: COMMAND_MENU_COMPONENT_INSTANCE_ID,
instanceIdToCopyTo: COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID,
expect(result.current.previousTargetedRecordsRule).toEqual({
mode: 'selection',
selectedRecordIds: [peopleMock[0].id, peopleMock[1].id],
});
expect(result.current.previousNumberOfSelectedRecords).toBe(2);
});
});

View file

@ -5,7 +5,6 @@ import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
import { renderHook } from '@testing-library/react';
import { Provider as JotaiProvider } from 'jotai';
import { act } from 'react';
import { RecoilRoot } from 'recoil';
import { CommandMenuPages } from 'twenty-shared/types';
import { IconArrowDown, IconDotsVertical } from 'twenty-ui/display';
@ -25,9 +24,7 @@ const mockedNavigationStack = [
];
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<JotaiProvider store={jotaiStore}>
<RecoilRoot>{children}</RecoilRoot>
</JotaiProvider>
<JotaiProvider store={jotaiStore}>{children}</JotaiProvider>
);
describe('useUpdateCommandMenuPageInfo', () => {

View file

@ -2,8 +2,8 @@ import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { addToNavPayloadRegistryStateV2 } from '@/navigation-menu-item/states/addToNavPayloadRegistryStateV2';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { addToNavPayloadRegistryState } from '@/navigation-menu-item/states/addToNavPayloadRegistryState';
import { useCloseAnyOpenDropdown } from '@/ui/layout/dropdown/hooks/useCloseAnyOpenDropdown';
import { emitSidePanelOpenEvent } from '@/ui/layout/right-drawer/utils/emitSidePanelOpenEvent';
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
@ -22,11 +22,11 @@ export const useCommandMenu = () => {
useRemoveFocusItemFromFocusStackById();
const closeCommandMenu = useCallback(() => {
const isCommandMenuOpened = store.get(isCommandMenuOpenedStateV2.atom);
const isCommandMenuOpened = store.get(isCommandMenuOpenedState.atom);
if (isCommandMenuOpened) {
store.set(addToNavPayloadRegistryStateV2.atom, new Map());
store.set(isCommandMenuOpenedStateV2.atom, false);
store.set(addToNavPayloadRegistryState.atom, new Map());
store.set(isCommandMenuOpenedState.atom, false);
store.set(isCommandMenuClosingState.atom, true);
closeAnyOpenDropdown();
removeFocusItemFromFocusStackById({
@ -47,7 +47,7 @@ export const useCommandMenu = () => {
}, [closeAnyOpenDropdown, navigateCommandMenu]);
const toggleCommandMenu = useCallback(() => {
const isCommandMenuOpened = store.get(isCommandMenuOpenedStateV2.atom);
const isCommandMenuOpened = store.get(isCommandMenuOpenedState.atom);
store.set(commandMenuSearchState.atom, '');

View file

@ -10,7 +10,7 @@ import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
@ -97,7 +97,7 @@ export const useCommandMenuCloseAnimationCompleteCleanup = () => {
Icon: undefined,
instanceId: '',
});
store.set(isCommandMenuOpenedStateV2.atom, false);
store.set(isCommandMenuOpenedState.atom, false);
store.set(commandMenuSearchState.atom, '');
store.set(commandMenuNavigationMorphItemsByPageState.atom, new Map());
store.set(commandMenuNavigationStackState.atom, []);

View file

@ -5,7 +5,7 @@ import { commandMenuNavigationStackState } from '@/command-menu/states/commandMe
import { CommandMenuPages } from 'twenty-shared/types';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { recordStoreIdentifiersFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreIdentifiersSelector';
import { recordStoreRecordsSelectorV2 } from '@/object-record/record-store/states/selectors/recordStoreRecordsSelectorV2';
import { recordStoreRecordsSelector } from '@/object-record/record-store/states/selectors/recordStoreRecordsSelector';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useMemo } from 'react';
@ -54,7 +54,7 @@ export const useCommandMenuContextChips = () => {
allowRequestsToTwentyIcons,
},
);
const records = useAtomFamilySelectorValue(recordStoreRecordsSelectorV2, {
const records = useAtomFamilySelectorValue(recordStoreRecordsSelector, {
recordIds: allRecordIds,
});

View file

@ -9,7 +9,7 @@ import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState
import { commandMenuShouldFocusTitleInputComponentState } from '@/command-menu/states/commandMenuShouldFocusTitleInputComponentState';
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { isCommandMenuClosingState } from '@/command-menu/states/isCommandMenuClosingState';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
@ -37,7 +37,7 @@ export const useNavigateCommandMenu = () => {
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const openCommandMenu = useCallback(() => {
const isCommandMenuOpened = store.get(isCommandMenuOpenedStateV2.atom);
const isCommandMenuOpened = store.get(isCommandMenuOpenedState.atom);
const isCommandMenuClosing = store.get(isCommandMenuClosingState.atom);
@ -65,7 +65,7 @@ export const useNavigateCommandMenu = () => {
instanceIdToCopyTo: COMMAND_MENU_COMPONENT_INSTANCE_ID,
});
store.set(isCommandMenuOpenedStateV2.atom, true);
store.set(isCommandMenuOpenedState.atom, true);
store.set(hasUserSelectedCommandState.atom, false);
}, [
copyContextStoreStates,

View file

@ -1,5 +1,5 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { t } from '@lingui/core/macro';
import { useCallback } from 'react';
@ -9,7 +9,7 @@ import { v4 } from 'uuid';
export const useOpenAskAIPageInCommandMenu = () => {
const { navigateCommandMenu } = useCommandMenu();
const isCommandMenuOpened = useAtomStateValue(isCommandMenuOpenedStateV2);
const isCommandMenuOpened = useAtomStateValue(isCommandMenuOpenedState);
const openAskAIPage = useCallback(
({

View file

@ -1,5 +1,5 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { t } from '@lingui/core/macro';
import { CommandMenuPages } from 'twenty-shared/types';
@ -8,7 +8,7 @@ import { v4 } from 'uuid';
export const useOpenRecordsSearchPageInCommandMenu = () => {
const { navigateCommandMenu } = useCommandMenu();
const isCommandMenuOpened = useAtomStateValue(isCommandMenuOpenedStateV2);
const isCommandMenuOpened = useAtomStateValue(isCommandMenuOpenedState);
const openRecordsSearchPage = () => {
navigateCommandMenu({

View file

@ -1,5 +1,5 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { viewableRichTextComponentStateV2 } from '@/command-menu/pages/rich-text-page/states/viewableRichTextComponentStateV2';
import { viewableRichTextComponentState } from '@/command-menu/pages/rich-text-page/states/viewableRichTextComponentState';
import { t } from '@lingui/core/macro';
import { useStore } from 'jotai';
import { useCallback } from 'react';
@ -19,7 +19,7 @@ export const useRichTextCommandMenu = () => {
activityId: string;
activityObjectNameSingular: string;
}) => {
store.set(viewableRichTextComponentStateV2.atom, {
store.set(viewableRichTextComponentState.atom, {
activityId,
activityObjectNameSingular,
});

View file

@ -1,81 +1,180 @@
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
import { COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuPreviousComponentInstanceId';
import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreFilterGroupsComponentState } from '@/context-store/states/contextStoreFilterGroupsComponentState';
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
import { contextStoreIsPageInEditModeComponentState } from '@/context-store/states/contextStoreIsPageInEditModeComponentState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
import { atom, useStore } from 'jotai';
import { useCallback } from 'react';
import { useStore } from 'jotai';
export const useSetGlobalCommandMenuContext = () => {
const store = useStore();
const { copyContextStoreStates } = useCopyContextStoreStates();
const setGlobalCommandMenuContext = useCallback(() => {
copyContextStoreStates({
instanceIdToCopyFrom: COMMAND_MENU_COMPONENT_INSTANCE_ID,
instanceIdToCopyTo: COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID,
});
store.set(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
atom(null, (get, batchSet) => {
const fromId = COMMAND_MENU_COMPONENT_INSTANCE_ID;
const toId = COMMAND_MENU_PREVIOUS_COMPONENT_INSTANCE_ID;
batchSet(
contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
instanceId: toId,
}),
get(
contextStoreCurrentObjectMetadataItemIdComponentState.atomFamily({
instanceId: fromId,
}),
),
);
batchSet(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: toId,
}),
get(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: fromId,
}),
),
);
batchSet(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: toId,
}),
get(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: fromId,
}),
),
);
batchSet(
contextStoreFiltersComponentState.atomFamily({
instanceId: toId,
}),
get(
contextStoreFiltersComponentState.atomFamily({
instanceId: fromId,
}),
),
);
batchSet(
contextStoreFilterGroupsComponentState.atomFamily({
instanceId: toId,
}),
get(
contextStoreFilterGroupsComponentState.atomFamily({
instanceId: fromId,
}),
),
);
batchSet(
contextStoreAnyFieldFilterValueComponentState.atomFamily({
instanceId: toId,
}),
get(
contextStoreAnyFieldFilterValueComponentState.atomFamily({
instanceId: fromId,
}),
),
);
batchSet(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId: toId,
}),
get(
contextStoreCurrentViewIdComponentState.atomFamily({
instanceId: fromId,
}),
),
);
batchSet(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: toId,
}),
get(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: fromId,
}),
),
);
batchSet(
contextStoreIsPageInEditModeComponentState.atomFamily({
instanceId: toId,
}),
get(
contextStoreIsPageInEditModeComponentState.atomFamily({
instanceId: fromId,
}),
),
);
batchSet(
contextStoreTargetedRecordsRuleComponentState.atomFamily({
instanceId: fromId,
}),
{ mode: 'selection', selectedRecordIds: [] },
);
batchSet(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: fromId,
}),
0,
);
batchSet(
contextStoreFiltersComponentState.atomFamily({
instanceId: fromId,
}),
[],
);
batchSet(
contextStoreFilterGroupsComponentState.atomFamily({
instanceId: fromId,
}),
[],
);
batchSet(
contextStoreAnyFieldFilterValueComponentState.atomFamily({
instanceId: fromId,
}),
'',
);
batchSet(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: fromId,
}),
ContextStoreViewType.Table,
);
batchSet(commandMenuPageInfoState.atom, {
title: undefined,
Icon: undefined,
instanceId: '',
});
batchSet(hasUserSelectedCommandState.atom, false);
}),
{
mode: 'selection',
selectedRecordIds: [],
},
);
store.set(
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
0,
);
store.set(
contextStoreFiltersComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
[],
);
store.set(
contextStoreFilterGroupsComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
[],
);
store.set(
contextStoreAnyFieldFilterValueComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
'',
);
store.set(
contextStoreCurrentViewTypeComponentState.atomFamily({
instanceId: COMMAND_MENU_COMPONENT_INSTANCE_ID,
}),
ContextStoreViewType.Table,
);
store.set(commandMenuPageInfoState.atom, {
title: undefined,
Icon: undefined,
instanceId: '',
});
store.set(hasUserSelectedCommandState.atom, false);
}, [copyContextStoreStates, store]);
}, [store]);
return {
setGlobalCommandMenuContext,

View file

@ -11,7 +11,7 @@ import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/ho
import { useSelectedNavigationMenuItemEditItemLabel } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemLabel';
import { useSelectedNavigationMenuItemEditItemObjectMetadata } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemObjectMetadata';
import { useUpdateLinkInDraft } from '@/navigation-menu-item/hooks/useUpdateLinkInDraft';
import { selectedNavigationMenuItemInEditModeStateV2 } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeStateV2';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useState } from 'react';
@ -31,7 +31,7 @@ export const CommandMenuNavigationMenuItemEditPage = () => {
const { t } = useLingui();
const selectedNavigationMenuItemInEditMode = useAtomStateValue(
selectedNavigationMenuItemInEditModeStateV2,
selectedNavigationMenuItemInEditModeState,
);
const { selectedItemLabel } = useSelectedNavigationMenuItemEditItemLabel();
const { selectedItem } = useSelectedNavigationMenuItemEditItem();

Some files were not shown because too many files have changed in this diff Show more