From 5ac5aab089306bd0745ea94b56d80b43fe28d8ed Mon Sep 17 00:00:00 2001 From: Nicolas Cantu Date: Tue, 6 Jan 2026 09:26:07 +0100 Subject: [PATCH] series wip & code quality checks --- .eslintrc.json | 68 +-- components/ArticleEditorForm.tsx | 30 +- components/AuthorCard.tsx | 2 +- components/AuthorPresentationEditor.tsx | 8 +- components/CategorySelect.tsx | 7 +- components/ConditionalPublishButton.tsx | 2 +- components/CreateAccountModalComponents.tsx | 39 +- components/CreateAccountModalSteps.tsx | 11 +- components/ImageUploadField.tsx | 2 +- components/KeyManagementManager.tsx | 8 +- components/MarkdownEditor.tsx | 6 +- components/NotificationPanel.tsx | 3 +- components/NotificationPanelHeader.tsx | 7 +- components/PageHeader.tsx | 2 +- components/PaymentModal.tsx | 25 +- eslint.config.mjs | 245 +++++++++ lib/accessControl.ts | 7 +- lib/nostrAuth.ts | 6 + lib/userContentSync.ts | 219 ++++++++ locales/en.txt | 25 + locales/fr.txt | 5 + package-lock.json | 533 ++++++++++---------- package.json | 9 +- public/locales/en.txt | 102 ++++ public/locales/fr.txt | 102 ++++ scripts/lint.js | 33 ++ 26 files changed, 1128 insertions(+), 378 deletions(-) create mode 100644 eslint.config.mjs create mode 100644 lib/userContentSync.ts create mode 100644 scripts/lint.js diff --git a/.eslintrc.json b/.eslintrc.json index 728abe1..78cdb35 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,56 +1,44 @@ { + "env": { + "browser": true, + "es2021": true, + "node": true + }, "extends": [ - "next/core-web-vitals", - "next/typescript" + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react/recommended", + "plugin:react-hooks/recommended" ], "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint", + "react", + "react-hooks" + ], "parserOptions": { - "ecmaVersion": 2020, + "ecmaVersion": "latest", "sourceType": "module", + "ecmaFeatures": { + "jsx": true + }, "project": "./tsconfig.json" }, + "settings": { + "react": { + "version": "detect" + } + }, "rules": { "@typescript-eslint/no-unused-vars": [ "error", { "argsIgnorePattern": "^_", - "varsIgnorePattern": "^_", - "caughtErrorsIgnorePattern": "^_" + "varsIgnorePattern": "^_" } ], - "@typescript-eslint/no-explicit-any": "error", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/no-floating-promises": "error", - "@typescript-eslint/no-misused-promises": "error", - "@typescript-eslint/await-thenable": "error", - "@typescript-eslint/no-unnecessary-type-assertion": "error", - "@typescript-eslint/no-non-null-assertion": "error", - "@typescript-eslint/prefer-nullish-coalescing": "error", - "@typescript-eslint/prefer-optional-chain": "error", - "@typescript-eslint/no-non-null-asserted-optional-chain": "error", - "no-console": ["warn", { "allow": ["warn", "error"] }], - "no-debugger": "error", - "no-alert": "error", - "prefer-const": "error", - "no-var": "error", - "object-shorthand": "error", - "prefer-arrow-callback": "warn", - "prefer-template": "error", - "eqeqeq": ["error", "always"], - "curly": ["error", "all"], - "no-throw-literal": "error", - "no-return-await": "error", - "require-await": "warn", - "no-await-in-loop": "warn", - "react-hooks/rules-of-hooks": "error", - "react-hooks/exhaustive-deps": "error", - "react/jsx-key": "error", - "react/jsx-no-duplicate-props": "error", - "react/jsx-no-undef": "error", - "react/no-unescaped-entities": "warn", - "react/no-unknown-property": "error", - "max-lines": ["error", { "max": 250, "skipBlankLines": false, "skipComments": false }], - "max-lines-per-function": ["error", { "max": 40, "skipBlankLines": false, "skipComments": false, "IIFEs": true }] + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-require-imports": "off", + "no-misleading-character-class": "off" } -} +} \ No newline at end of file diff --git a/components/ArticleEditorForm.tsx b/components/ArticleEditorForm.tsx index 9b20179..14d5386 100644 --- a/components/ArticleEditorForm.tsx +++ b/components/ArticleEditorForm.tsx @@ -6,6 +6,7 @@ import { ArticleFormButtons } from './ArticleFormButtons' import { CategorySelect } from './CategorySelect' import { MarkdownEditor } from './MarkdownEditor' import type { MediaRef } from '@/types/nostr' +import { t } from '@/lib/i18n' interface ArticleEditorFormProps { draft: ArticleDraft @@ -28,11 +29,11 @@ function CategoryField({ return ( ) } @@ -98,11 +99,11 @@ function ArticleTitleField({ draft, onDraftChange }: { draft: ArticleDraft; onDr return ( onDraftChange({ ...draft, title: value as string })} required - placeholder="Entrez le titre de l'article" + placeholder={t('article.editor.title.placeholder')} /> ) } @@ -117,14 +118,14 @@ function ArticlePreviewField({ return ( onDraftChange({ ...draft, preview: value as string })} required type="textarea" rows={4} - placeholder="Cet aperçu sera visible par tous gratuitement" - helpText="Ce contenu sera visible par tous" + placeholder={t('article.editor.preview.placeholder')} + helpText={t('article.editor.preview.help')} /> ) } @@ -145,7 +146,7 @@ function SeriesSelect({ return (
{helpText &&

{helpText}

}
diff --git a/components/ConditionalPublishButton.tsx b/components/ConditionalPublishButton.tsx index a8dc864..06e3745 100644 --- a/components/ConditionalPublishButton.tsx +++ b/components/ConditionalPublishButton.tsx @@ -21,7 +21,7 @@ function AuthorProfileLink({ presentation, profile }: { presentation: Article; p // Title format: "Présentation de " or just use profile name let authorName = presentation.title.replace(/^Présentation de /, '').trim() if (!authorName || authorName === 'Présentation') { - authorName = profile?.name || 'Auteur' + authorName = profile?.name || t('common.author') } // Extract picture from presentation (bannerUrl or from JSON metadata) or profile diff --git a/components/CreateAccountModalComponents.tsx b/components/CreateAccountModalComponents.tsx index b1316ab..3887589 100644 --- a/components/CreateAccountModalComponents.tsx +++ b/components/CreateAccountModalComponents.tsx @@ -1,18 +1,12 @@ +import { t } from '@/lib/i18n' export function RecoveryWarning() { return (
-

⚠️ Important

-

- Ces 4 mots-clés sont votre seule façon de récupérer votre compte. - Ils ne seront jamais affichés à nouveau. -

-

- Ces mots-clés (dictionnaire BIP39) sont utilisés avec PBKDF2 pour chiffrer une clé de chiffrement (KEK) stockée dans l'API Credentials du navigateur. Cette KEK chiffre ensuite votre clé privée stockée dans IndexedDB (système à deux niveaux). -

-

- Notez-les dans un endroit sûr. Sans ces mots-clés, vous perdrez définitivement l'accès à votre compte. -

+

{t('account.create.recovery.warning.title')}

+

+

+

{t('account.create.recovery.warning.part3')}

) } @@ -45,7 +39,7 @@ export function RecoveryPhraseDisplay({ }} className="w-full py-2 px-4 bg-cyber-light border border-neon-cyan/30 hover:border-neon-cyan/50 hover:bg-cyber-dark text-cyber-accent hover:text-neon-cyan rounded-lg text-sm font-medium transition-colors" > - {copied ? '✓ Copié!' : 'Copier les mots-clés'} + {copied ? t('account.create.recovery.copied') : t('account.create.recovery.copy')} ) @@ -54,7 +48,7 @@ export function RecoveryPhraseDisplay({ export function PublicKeyDisplay({ npub }: { npub: string }) { return (
-

Votre clé publique (npub)

+

{t('account.create.publicKey')}

{npub}

) @@ -73,20 +67,17 @@ export function ImportKeyForm({ <>