9d57bc39e5
## Summary Fully replaces ESLint with OxLint across the entire monorepo: - **Replaced all ESLint configs** (`eslint.config.mjs`) with OxLint configs (`.oxlintrc.json`) for every package: `twenty-front`, `twenty-server`, `twenty-emails`, `twenty-ui`, `twenty-shared`, `twenty-sdk`, `twenty-zapier`, `twenty-docs`, `twenty-website`, `twenty-apps/*`, `create-twenty-app` - **Migrated custom lint rules** from ESLint plugin format to OxLint JS plugin system (`@oxlint/plugins`), including `styled-components-prefixed-with-styled`, `no-hardcoded-colors`, `sort-css-properties-alphabetically`, `graphql-resolvers-should-be-guarded`, `rest-api-methods-should-be-guarded`, `max-consts-per-file`, and Jotai-related rules - **Migrated custom rule tests** from ESLint `RuleTester` + Jest to `oxlint/plugins-dev` `RuleTester` + Vitest - **Removed all ESLint dependencies** from `package.json` files and regenerated lockfiles - **Updated Nx targets** (`lint`, `lint:diff-with-main`, `fmt`) in `nx.json` and per-project `project.json` to use `oxlint` commands with proper `dependsOn` for plugin builds - **Updated CI workflows** (`.github/workflows/ci-*.yaml`) — no more ESLint executor - **Updated IDE setup**: replaced `dbaeumer.vscode-eslint` with `oxc.oxc-vscode` extension, configured `source.fixAll.oxc` and format-on-save with Prettier - **Replaced all `eslint-disable` comments** with `oxlint-disable` equivalents across the codebase - **Updated docs** (`twenty-docs`) to reference OxLint instead of ESLint - **Renamed** `twenty-eslint-rules` package to `twenty-oxlint-rules` ### Temporarily disabled rules (tracked in `OXLINT_MIGRATION_TODO.md`) | Rule | Package | Violations | Auto-fixable | |------|---------|-----------|-------------| | `twenty/sort-css-properties-alphabetically` | twenty-front | 578 | Yes | | `typescript/consistent-type-imports` | twenty-server | 3814 | Yes | | `twenty/max-consts-per-file` | twenty-server | 94 | No | ### Dropped plugins (no OxLint equivalent) `eslint-plugin-project-structure`, `lingui/*`, `@stylistic/*`, `import/order`, `prefer-arrow/prefer-arrow-functions`, `eslint-plugin-mdx`, `@next/eslint-plugin-next`, `eslint-plugin-storybook`, `eslint-plugin-react-refresh`. Partial coverage for `jsx-a11y` and `unused-imports`. ### Additional fixes (pre-existing issues exposed by merge) - Fixed `EmailThreadPreview.tsx` broken import from main rename (`useOpenEmailThreadInSidePanel`) - Restored truthiness guard in `getActivityTargetObjectRecords.ts` - Fixed `AgentTurnResolver` return types to match entity (virtual `fileMediaType`/`fileUrl` are resolved via `@ResolveField()`) ## Test plan - [x] `npx nx lint twenty-front` passes - [x] `npx nx lint twenty-server` passes - [x] `npx nx lint twenty-docs` passes - [x] Custom oxlint rules validated with Vitest: `npx nx test twenty-oxlint-rules` - [x] `npx nx typecheck twenty-front` passes - [x] `npx nx typecheck twenty-server` passes - [x] CI workflows trigger correctly with `dependsOn: ["twenty-oxlint-rules:build"]` - [x] IDE linting works with `oxc.oxc-vscode` extension
200 lines
5.4 KiB
TypeScript
200 lines
5.4 KiB
TypeScript
import { styled } from '@linaria/react';
|
|
import { type ReactNode, useState } from 'react';
|
|
import { useDebouncedCallback } from 'use-debounce';
|
|
|
|
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
|
|
|
import { ModalStatefulWrapper } from '@/ui/layout/modal/components/ModalStatefulWrapper';
|
|
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
|
import { msg } from '@lingui/core/macro';
|
|
import { useLingui } from '@lingui/react/macro';
|
|
import { H1Title, H1TitleFontColor } from 'twenty-ui/display';
|
|
import { Button, type ButtonAccent } from 'twenty-ui/input';
|
|
import {
|
|
Section,
|
|
SectionAlignment,
|
|
SectionFontColor,
|
|
type ModalOverlay,
|
|
} from 'twenty-ui/layout';
|
|
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
|
|
|
export type ConfirmationModalProps = {
|
|
modalInstanceId: string;
|
|
title: string;
|
|
loading?: boolean;
|
|
subtitle: ReactNode;
|
|
onClose?: () => void;
|
|
onConfirmClick: () => void;
|
|
confirmButtonText?: string;
|
|
confirmationPlaceholder?: string;
|
|
confirmationValue?: string;
|
|
confirmButtonAccent?: ButtonAccent;
|
|
AdditionalButtons?: React.ReactNode;
|
|
overlay?: ModalOverlay;
|
|
};
|
|
|
|
const StyledCenteredButtonContainer = styled.div`
|
|
box-sizing: border-box;
|
|
margin-top: ${themeCssVariables.spacing[2]};
|
|
`;
|
|
|
|
export const StyledCenteredButton = (
|
|
props: React.ComponentProps<typeof Button>,
|
|
) => (
|
|
<StyledCenteredButtonContainer>
|
|
{/* oxlint-disable-next-line react/jsx-props-no-spreading */}
|
|
<Button {...props} />
|
|
</StyledCenteredButtonContainer>
|
|
);
|
|
|
|
const StyledCenteredTitle = styled.div`
|
|
text-align: center;
|
|
`;
|
|
|
|
const StyledSectionContainer = styled.div`
|
|
margin-bottom: ${themeCssVariables.spacing[6]};
|
|
`;
|
|
|
|
const StyledConfirmationButtonContainer = styled.div`
|
|
box-sizing: border-box;
|
|
margin-top: ${themeCssVariables.spacing[2]};
|
|
> button {
|
|
border-color: ${themeCssVariables.border.color.danger};
|
|
box-shadow: none;
|
|
color: ${themeCssVariables.color.red};
|
|
font-size: ${themeCssVariables.font.size.md};
|
|
line-height: ${themeCssVariables.text.lineHeight.lg};
|
|
&:hover {
|
|
background-color: ${themeCssVariables.color.red3};
|
|
}
|
|
}
|
|
`;
|
|
|
|
export const StyledConfirmationButton = (
|
|
props: React.ComponentProps<typeof Button>,
|
|
) => (
|
|
<StyledConfirmationButtonContainer>
|
|
{/* oxlint-disable-next-line react/jsx-props-no-spreading */}
|
|
<Button {...props} />
|
|
</StyledConfirmationButtonContainer>
|
|
);
|
|
|
|
const defaultConfirmButtonText = msg`Confirm`;
|
|
|
|
export const ConfirmationModal = ({
|
|
modalInstanceId,
|
|
title,
|
|
loading,
|
|
subtitle,
|
|
onConfirmClick,
|
|
onClose,
|
|
confirmButtonText,
|
|
confirmationValue,
|
|
confirmationPlaceholder,
|
|
confirmButtonAccent = 'danger',
|
|
AdditionalButtons,
|
|
overlay = 'dark',
|
|
}: ConfirmationModalProps) => {
|
|
const { i18n, t } = useLingui();
|
|
const translatedConfirmButtonText =
|
|
confirmButtonText ?? i18n._(defaultConfirmButtonText);
|
|
const [inputConfirmationValue, setInputConfirmationValue] =
|
|
useState<string>('');
|
|
const [isValidValue, setIsValidValue] = useState(!confirmationValue);
|
|
|
|
const handleInputConfimrationValueChange = (value: string) => {
|
|
setInputConfirmationValue(value);
|
|
isValueMatchingInput(confirmationValue, value);
|
|
};
|
|
|
|
const isValueMatchingInput = useDebouncedCallback(
|
|
(value?: string, inputValue?: string) => {
|
|
setIsValidValue(Boolean(value && inputValue && value === inputValue));
|
|
},
|
|
250,
|
|
);
|
|
|
|
const { closeModal } = useModal();
|
|
|
|
const handleConfirmClick = () => {
|
|
closeModal(modalInstanceId);
|
|
onConfirmClick();
|
|
};
|
|
|
|
const handleCancelClick = () => {
|
|
closeModal(modalInstanceId);
|
|
onClose?.();
|
|
};
|
|
|
|
const handleEnter = () => {
|
|
if (isValidValue) {
|
|
handleConfirmClick();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<ModalStatefulWrapper
|
|
modalInstanceId={modalInstanceId}
|
|
onClose={() => {
|
|
onClose?.();
|
|
}}
|
|
onEnter={handleEnter}
|
|
isClosable={true}
|
|
padding="large"
|
|
overlay={overlay}
|
|
dataGloballyPreventClickOutside
|
|
renderInDocumentBody
|
|
smallBorderRadius
|
|
narrowWidth
|
|
autoHeight
|
|
>
|
|
<StyledCenteredTitle>
|
|
<H1Title title={title} fontColor={H1TitleFontColor.Primary} />
|
|
</StyledCenteredTitle>
|
|
<StyledSectionContainer>
|
|
<Section
|
|
alignment={SectionAlignment.Center}
|
|
fontColor={SectionFontColor.Primary}
|
|
>
|
|
{subtitle}
|
|
</Section>
|
|
</StyledSectionContainer>
|
|
{confirmationValue && (
|
|
<Section>
|
|
<SettingsTextInput
|
|
instanceId="confirmation-modal-input"
|
|
dataTestId="confirmation-modal-input"
|
|
value={inputConfirmationValue}
|
|
onChange={handleInputConfimrationValueChange}
|
|
placeholder={confirmationPlaceholder}
|
|
fullWidth
|
|
disableHotkeys
|
|
key={'input-' + confirmationValue}
|
|
/>
|
|
</Section>
|
|
)}
|
|
<StyledCenteredButton
|
|
onClick={handleCancelClick}
|
|
variant="secondary"
|
|
title={t`Cancel`}
|
|
fullWidth
|
|
justify="center"
|
|
dataTestId="confirmation-modal-cancel-button"
|
|
/>
|
|
|
|
{AdditionalButtons}
|
|
|
|
<StyledCenteredButton
|
|
onClick={handleConfirmClick}
|
|
variant="secondary"
|
|
accent={confirmButtonAccent}
|
|
title={translatedConfirmButtonText}
|
|
disabled={!isValidValue || loading}
|
|
fullWidth
|
|
justify="center"
|
|
dataTestId="confirmation-modal-confirm-button"
|
|
/>
|
|
</ModalStatefulWrapper>
|
|
);
|
|
};
|