diff --git a/apps/admin-x-settings/.eslintrc.cjs b/apps/admin-x-settings/.eslintrc.cjs index 0f6166bbd5..2f13138b49 100644 --- a/apps/admin-x-settings/.eslintrc.cjs +++ b/apps/admin-x-settings/.eslintrc.cjs @@ -2,24 +2,44 @@ module.exports = { root: true, extends: [ - 'react-app', - 'plugin:ghost/browser' + 'plugin:ghost/ts', + 'plugin:react/recommended', + 'plugin:react-hooks/recommended' ], plugins: [ 'ghost', + 'react-refresh', 'tailwindcss' ], + settings: { + react: { + version: 'detect' + } + }, rules: { // sort multiple import lines into alphabetical groups 'ghost/sort-imports-es6-autofix/sort-imports-es6': ['error', { memberSyntaxSortOrder: ['none', 'all', 'single', 'multiple'] }], + // TODO: enable this when we have the time to retroactively go and fix the issues + 'prefer-const': 'off', + + // TODO: re-enable this (maybe fixed fast refresh?) + 'react-refresh/only-export-components': 'off', + // suppress errors for missing 'import React' in JSX files, as we don't need it 'react/react-in-jsx-scope': 'off', // ignore prop-types for now 'react/prop-types': 'off', + // TODO: re-enable this because otherwise we're just skirting TS + '@typescript-eslint/no-explicit-any': 'warn', + + // TODO: re-enable these if deemed useful + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-empty-function': 'off', + // custom react rules 'react/jsx-sort-props': ['error', { reservedFirst: true, @@ -29,6 +49,7 @@ module.exports = { }], 'react/button-has-type': 'error', 'react/no-array-index-key': 'error', + 'react/jsx-key': 'off', 'tailwindcss/classnames-order': ['error', {config: 'tailwind.config.cjs'}], 'tailwindcss/enforces-negative-arbitrary-values': ['warn', {config: 'tailwind.config.cjs'}], diff --git a/apps/admin-x-settings/src/admin-x-ds/global/List.stories.tsx b/apps/admin-x-settings/src/admin-x-ds/global/List.stories.tsx index 189bbe479f..7c7839ecf0 100644 --- a/apps/admin-x-settings/src/admin-x-ds/global/List.stories.tsx +++ b/apps/admin-x-settings/src/admin-x-ds/global/List.stories.tsx @@ -13,7 +13,7 @@ const meta = { export default meta; type Story = StoryObj; -const {id, ...listItemProps} = ListItemStories.HiddenActions.args || {}; +const {/*id,*/ ...listItemProps} = ListItemStories.HiddenActions.args || {}; const listItems = ( <> diff --git a/apps/admin-x-settings/src/admin-x-ds/global/SortableList.tsx b/apps/admin-x-settings/src/admin-x-ds/global/SortableList.tsx index ad36ae3f30..b14f6a194c 100644 --- a/apps/admin-x-settings/src/admin-x-ds/global/SortableList.tsx +++ b/apps/admin-x-settings/src/admin-x-ds/global/SortableList.tsx @@ -12,6 +12,8 @@ export interface SortableItemContainerProps { setRef?: (element: HTMLElement | null) => void; isDragging: boolean; dragHandleAttributes?: DraggableAttributes; + // TODO: figure out a stricter alternative for Function + // eslint-disable-next-line @typescript-eslint/ban-types dragHandleListeners?: Record; dragHandleClass?: string; style?: React.CSSProperties; diff --git a/apps/admin-x-settings/src/admin-x-ds/global/form/FileUpload.tsx b/apps/admin-x-settings/src/admin-x-ds/global/form/FileUpload.tsx index ee1700ebad..a35a23348e 100644 --- a/apps/admin-x-settings/src/admin-x-ds/global/form/FileUpload.tsx +++ b/apps/admin-x-settings/src/admin-x-ds/global/form/FileUpload.tsx @@ -1,4 +1,4 @@ -import React, {ChangeEvent, useState} from 'react'; +import React, {CSSProperties, ChangeEvent, useState} from 'react'; export interface FileUploadProps { id: string; @@ -11,7 +11,7 @@ export interface FileUploadProps { children?: string | React.ReactNode; className?: string; onUpload: (file: File) => void; - style?: {} + style?: CSSProperties; unstyled?: boolean; } @@ -39,4 +39,4 @@ const FileUpload: React.FC = ({id, onUpload, children, style, u ); }; -export default FileUpload; \ No newline at end of file +export default FileUpload; diff --git a/apps/admin-x-settings/src/admin-x-ds/global/form/MultiSelect.tsx b/apps/admin-x-settings/src/admin-x-ds/global/form/MultiSelect.tsx index dfe7beadc2..a3b98fd7e6 100644 --- a/apps/admin-x-settings/src/admin-x-ds/global/form/MultiSelect.tsx +++ b/apps/admin-x-settings/src/admin-x-ds/global/form/MultiSelect.tsx @@ -76,6 +76,8 @@ const MultiSelect: React.FC = ({ }; const dropdownIndicatorComponent = useMemo(() => { + // TODO: fix "Component definition is missing display name" + // eslint-disable-next-line react/display-name return (ddiProps: DropdownIndicatorProps) => ; }, [clearBg]); diff --git a/apps/admin-x-settings/src/admin-x-ds/global/form/TextField.tsx b/apps/admin-x-settings/src/admin-x-ds/global/form/TextField.tsx index 6a6b6f59fa..f9057bad94 100644 --- a/apps/admin-x-settings/src/admin-x-ds/global/form/TextField.tsx +++ b/apps/admin-x-settings/src/admin-x-ds/global/form/TextField.tsx @@ -29,7 +29,7 @@ const TextField: React.FC = ({ type = 'text', inputRef, title, - titleColor = 'grey', + //titleColor = 'grey', hideTitle, value, error, diff --git a/apps/admin-x-settings/src/admin-x-ds/global/modal/ModalContainer.tsx b/apps/admin-x-settings/src/admin-x-ds/global/modal/ModalContainer.tsx index cb1f87c99e..57f7e71f60 100644 --- a/apps/admin-x-settings/src/admin-x-ds/global/modal/ModalContainer.tsx +++ b/apps/admin-x-settings/src/admin-x-ds/global/modal/ModalContainer.tsx @@ -4,7 +4,7 @@ import React from 'react'; import Button from '../Button'; import Modal, {ModalProps} from './Modal'; -const ModalContainer: React.FC = ({children, onCancel, ...props}) => { +const ModalContainer: React.FC = ({children, ...props}) => { const modal = NiceModal.create(() => { return ( @@ -23,4 +23,4 @@ const ModalContainer: React.FC = ({children, onCancel, ...props}) => ); }; -export default ModalContainer; \ No newline at end of file +export default ModalContainer; diff --git a/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModal.tsx b/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModal.tsx index b4604f47b1..82fd25953a 100644 --- a/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModal.tsx +++ b/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModal.tsx @@ -16,6 +16,8 @@ import TextField from '../../../../admin-x-ds/global/form/TextField'; import Toggle from '../../../../admin-x-ds/global/form/Toggle'; import {PreviewModalContent} from '../../../../admin-x-ds/global/modal/PreviewModal'; +// TODO: do we need this interface? +// eslint-disable-next-line @typescript-eslint/no-empty-interface interface NewsletterDetailModalProps { } @@ -213,4 +215,4 @@ const NewsletterDetailModal: React.FC = () => { />; }; -export default NiceModal.create(NewsletterDetailModal); \ No newline at end of file +export default NiceModal.create(NewsletterDetailModal); diff --git a/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterPreview.tsx b/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterPreview.tsx index fb6d8276f9..89f7a7ebc0 100644 --- a/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterPreview.tsx +++ b/apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterPreview.tsx @@ -43,7 +43,7 @@ const NewsletterPreview: React.FC = () => {

This is what your content will look like when you send one of your posts as an email newsletter to your subscribers.

-

Over there on the left you'll see some settings that allow you to customize the look and feel of this template to make it perfectly suited to your brand. Email templates are exceptionally finnicky to make, but we've spent a long time optimising this one to make it work beautifully across devices, email clients and content types.

+

Over there on the left you'll see some settings that allow you to customize the look and feel of this template to make it perfectly suited to your brand. Email templates are exceptionally finnicky to make, but we've spent a long time optimising this one to make it work beautifully across devices, email clients and content types.

So, you can trust that every email you send with Ghost will look great and work well. Just like the rest of your site.

@@ -140,4 +140,4 @@ const NewsletterPreview: React.FC = () => { ); }; -export default NewsletterPreview; \ No newline at end of file +export default NewsletterPreview; diff --git a/apps/admin-x-settings/src/components/settings/membership/portal/PortalModal.tsx b/apps/admin-x-settings/src/components/settings/membership/portal/PortalModal.tsx index 9f265bae1b..06b62b5a06 100644 --- a/apps/admin-x-settings/src/components/settings/membership/portal/PortalModal.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/portal/PortalModal.tsx @@ -87,7 +87,7 @@ const PortalModal: React.FC = () => { NiceModal.show(ConfirmationModal, { title: 'Confirm email address', prompt: <> - We've sent a confirmation email to {newEmail}. + We've sent a confirmation email to {newEmail}. Until verified, your support address will remain {fullEmailAddress(currentEmail?.toString() || 'noreply', siteData!)}. , okLabel: 'Close', diff --git a/apps/admin-x-settings/src/components/settings/membership/portal/SignupOptions.tsx b/apps/admin-x-settings/src/components/settings/membership/portal/SignupOptions.tsx index e0253c46af..24f3273dac 100644 --- a/apps/admin-x-settings/src/components/settings/membership/portal/SignupOptions.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/portal/SignupOptions.tsx @@ -121,7 +121,7 @@ const SignupOptions: React.FC<{ Recommended: 115 characters. You've used {signupTermsLength}} + hint={errors.portal_signup_terms_html || <>Recommended: 115 characters. You've used {signupTermsLength}} nodes='MINIMAL_NODES' placeholder={`By signing up, I agree to receive emails from ...`} title='Display notice at signup' diff --git a/apps/admin-x-settings/src/components/settings/membership/stripe/StripeConnectModal.tsx b/apps/admin-x-settings/src/components/settings/membership/stripe/StripeConnectModal.tsx index d4843efad7..0235c3410e 100644 --- a/apps/admin-x-settings/src/components/settings/membership/stripe/StripeConnectModal.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/stripe/StripeConnectModal.tsx @@ -23,7 +23,7 @@ const Start: React.FC<{onNext?: () => void}> = ({onNext}) => {
Stripe is our exclusive direct payments partner. Ghost collects no fees on any payments! If you don’t have a Stripe account yet, you can sign up here.
- I have a Stripe account, let's go →} onClick={onNext} /> + I have a Stripe account, let's go →} onClick={onNext} /> ); }; @@ -158,4 +158,4 @@ const StripeConnectModal: React.FC = () => {
; }; -export default NiceModal.create(StripeConnectModal); \ No newline at end of file +export default NiceModal.create(StripeConnectModal); diff --git a/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx b/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx index e168dd065c..8f8867a602 100644 --- a/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx +++ b/apps/admin-x-settings/src/components/settings/membership/tiers/TierDetailModal.tsx @@ -65,7 +65,7 @@ const TierDetailModal: React.FC = ({tier}) => { values.currency = currency; values.monthly_price = currencyFromDecimal(parseFloat(monthlyPrice)); values.yearly_price = currencyFromDecimal(parseFloat(yearlyPrice)); - values.trial_days = parseInt(formState.trial_days); + values.trial_days = parseInt(trialDays); } if (tier?.id) { diff --git a/apps/admin-x-settings/src/components/settings/site/ThemeModal.tsx b/apps/admin-x-settings/src/components/settings/site/ThemeModal.tsx index 487533140b..e0fb0b84f8 100644 --- a/apps/admin-x-settings/src/components/settings/site/ThemeModal.tsx +++ b/apps/admin-x-settings/src/components/settings/site/ThemeModal.tsx @@ -85,7 +85,7 @@ async function handleThemeUpload({ title = `Upload successful with ${hasErrors ? 'errors' : 'warnings'}`; prompt = <> - The theme "{uploadedTheme.name}" was installed successfully but we detected some {hasErrors ? 'errors' : 'warnings'}. + The theme "{uploadedTheme.name}" was installed successfully but we detected some {hasErrors ? 'errors' : 'warnings'}. ; if (!uploadedTheme.active) { @@ -250,7 +250,7 @@ const ChangeThemeModal = NiceModal.create(() => { title = `Installed with ${hasErrors ? 'errors' : 'warnings'}`; prompt = <> - The theme "{newlyInstalledTheme.name}" was installed successfully but we detected some {hasErrors ? 'errors' : 'warnings'}. + The theme "{newlyInstalledTheme.name}" was installed successfully but we detected some {hasErrors ? 'errors' : 'warnings'}. ; if (!newlyInstalledTheme.active) { diff --git a/apps/admin-x-settings/src/hooks/useForm.tsx b/apps/admin-x-settings/src/hooks/useForm.tsx index 1467eaf36e..8075ce9a6e 100644 --- a/apps/admin-x-settings/src/hooks/useForm.tsx +++ b/apps/admin-x-settings/src/hooks/useForm.tsx @@ -21,6 +21,8 @@ export interface FormHook { reset: () => void; } +// TODO: figure out if we need to extend `any`? +// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint const useForm = ({initialState, onSave}: { initialState: State, onSave: () => void | Promise diff --git a/apps/admin-x-settings/src/hooks/useSortableIndexedList.tsx b/apps/admin-x-settings/src/hooks/useSortableIndexedList.tsx index dbed340178..a547b7facc 100644 --- a/apps/admin-x-settings/src/hooks/useSortableIndexedList.tsx +++ b/apps/admin-x-settings/src/hooks/useSortableIndexedList.tsx @@ -11,6 +11,8 @@ export type SortableIndexedList = { setNewItem: (item: Item) => void; } +// TODO: figure out if we need to extend `unknown`? +// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint const useSortableIndexedList = ({items, setItems, blank, canAddNewItem}: { items: Item[]; setItems: (newItems: Item[]) => void; diff --git a/apps/admin-x-settings/src/utils/helpers.ts b/apps/admin-x-settings/src/utils/helpers.ts index 26d97b630a..613fb9d8ce 100644 --- a/apps/admin-x-settings/src/utils/helpers.ts +++ b/apps/admin-x-settings/src/utils/helpers.ts @@ -51,7 +51,7 @@ export function generateAvatarColor(name: string) { const s = 70; const l = 40; let hash = 0; - for (var i = 0; i < name.length; i++) { + for (let i = 0; i < name.length; i++) { hash = name.charCodeAt(i) + ((hash << 5) - hash); } @@ -146,4 +146,4 @@ export function getArchivedTiers(tiers: Tier[]) { export function numberWithCommas(x: number) { return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); -} \ No newline at end of file +} diff --git a/apps/announcement-bar/package.json b/apps/announcement-bar/package.json index eca1333a78..335192274b 100644 --- a/apps/announcement-bar/package.json +++ b/apps/announcement-bar/package.json @@ -36,14 +36,29 @@ "prepublishOnly": "yarn build" }, "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest", - "plugin:ghost/browser" - ], - "plugins": [ - "ghost" - ] + "env": { + "browser": true, + "jest": true + }, + "parserOptions": { + "sourceType": "module", + "ecmaVersion": 2022 + }, + "extends": [ + "plugin:ghost/browser", + "plugin:react/recommended" + ], + "plugins": [ + "ghost" + ], + "rules": { + "react/prop-types": "off" + }, + "settings": { + "react": { + "version": "detect" + } + } }, "browserslist": { "production": [ @@ -66,8 +81,6 @@ }, "devDependencies": { "@vitejs/plugin-react": "4.0.3", - "eslint-plugin-jest": "27.2.3", - "eslint-plugin-react": "7.33.0", "vite": "4.4.7", "vite-plugin-svgr": "3.2.0", "vitest": "0.33.0" diff --git a/apps/comments-ui/.eslintrc.js b/apps/comments-ui/.eslintrc.js index 996726ec69..d9c2725353 100644 --- a/apps/comments-ui/.eslintrc.js +++ b/apps/comments-ui/.eslintrc.js @@ -2,19 +2,27 @@ module.exports = { root: true, extends: [ - 'react-app', - 'plugin:ghost/browser' + 'plugin:ghost/ts', + 'plugin:react/recommended' ], plugins: [ 'ghost', 'tailwindcss' ], + settings: { + react: { + version: 'detect' + } + }, rules: { // sort multiple import lines into alphabetical groups 'ghost/sort-imports-es6-autofix/sort-imports-es6': ['error', { memberSyntaxSortOrder: ['none', 'all', 'single', 'multiple'] }], + // TODO: fix + remove this + '@typescript-eslint/no-explicit-any': 'warn', + // suppress errors for missing 'import React' in JSX files, as we don't need it 'react/react-in-jsx-scope': 'off', // ignore prop-types for now diff --git a/apps/comments-ui/src/AppContext.ts b/apps/comments-ui/src/AppContext.ts index 33aa3fd8dd..e2b1bcdc10 100644 --- a/apps/comments-ui/src/AppContext.ts +++ b/apps/comments-ui/src/AppContext.ts @@ -70,6 +70,7 @@ export type AppContextType = { popup: Page | null, // This part makes sure we can add automatic data and return types to the actions when using context.dispatchAction('actionName', data) + // eslint-disable-next-line @typescript-eslint/ban-types dispatchAction: (action: T, data: Parameters<(typeof Actions & typeof SyncActions)[T]>[0] extends {data: any} ? Parameters<(typeof Actions & typeof SyncActions)[T]>[0]['data'] : {}) => T extends ActionType ? Promise : void } diff --git a/apps/comments-ui/src/actions.ts b/apps/comments-ui/src/actions.ts index a9e6d02cb7..207e6a0652 100644 --- a/apps/comments-ui/src/actions.ts +++ b/apps/comments-ui/src/actions.ts @@ -177,7 +177,7 @@ async function likeComment({state, api, data: comment}: {state: AppContextType, }; } -async function reportComment({state, api, data: comment}: {state: AppContextType, api: GhostApi, data: {id: string}}) { +async function reportComment({api, data: comment}: {api: GhostApi, data: {id: string}}) { await api.comments.report({comment}); return {}; diff --git a/apps/comments-ui/src/components/ContentBox.tsx b/apps/comments-ui/src/components/ContentBox.tsx index e2885eae5b..bdafe8c1c1 100644 --- a/apps/comments-ui/src/components/ContentBox.tsx +++ b/apps/comments-ui/src/components/ContentBox.tsx @@ -9,7 +9,7 @@ type Props = { }; const ContentBox: React.FC = ({done}) => { const luminance = (r: number, g: number, b: number) => { - var a = [r, g, b].map(function (v) { + const a = [r, g, b].map(function (v) { v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); }); @@ -17,10 +17,10 @@ const ContentBox: React.FC = ({done}) => { }; const contrast = (rgb1: [number, number, number], rgb2: [number, number, number]) => { - var lum1 = luminance(rgb1[0], rgb1[1], rgb1[2]); - var lum2 = luminance(rgb2[0], rgb2[1], rgb2[2]); - var brightest = Math.max(lum1, lum2); - var darkest = Math.min(lum1, lum2); + const lum1 = luminance(rgb1[0], rgb1[1], rgb1[2]); + const lum2 = luminance(rgb2[0], rgb2[1], rgb2[2]); + const brightest = Math.max(lum1, lum2); + const darkest = Math.min(lum1, lum2); return (brightest + 0.05) / (darkest + 0.05); }; const {accentColor, colorScheme} = useAppContext(); diff --git a/apps/comments-ui/src/components/IFrame.tsx b/apps/comments-ui/src/components/IFrame.tsx index d22e44412a..ef328261a8 100644 --- a/apps/comments-ui/src/components/IFrame.tsx +++ b/apps/comments-ui/src/components/IFrame.tsx @@ -36,6 +36,7 @@ export default class IFrame extends Component { this.forceUpdate(); if (this.props.onResize) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars (new ResizeObserver(_ => this.props.onResize(this.iframeRoot)))?.observe?.(this.iframeRoot); } @@ -57,7 +58,7 @@ export default class IFrame extends Component { } render() { - const {children, head, title = '', style = {}, onResize, ...rest} = this.props; + const {children, head, title = '', style = {}, ...rest} = this.props; return (