diff --git a/.github/workflows/deploy.docs.yml b/.github/workflows/deploy.docs.yml
index 2c0ec25e..9d21e5c5 100644
--- a/.github/workflows/deploy.docs.yml
+++ b/.github/workflows/deploy.docs.yml
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
- - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
+ - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
with:
node-version-file: '.tool-versions'
- name: Install Dependencies & Build Library
diff --git a/.github/workflows/test.backend.seed.yml b/.github/workflows/test.backend.seed.yml
index 8bb77e5e..2ac02761 100644
--- a/.github/workflows/test.backend.seed.yml
+++ b/.github/workflows/test.backend.seed.yml
@@ -30,7 +30,7 @@ jobs:
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
- name: Setup Node.js
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
+ uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version-file: '.tool-versions'
diff --git a/.github/workflows/test.build.lib.yml b/.github/workflows/test.build.lib.yml
index fbf76fe4..f98ed1b1 100644
--- a/.github/workflows/test.build.lib.yml
+++ b/.github/workflows/test.build.lib.yml
@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
- - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
+ - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
with:
node-version-file: '.tool-versions'
- name: Install Dependencies & Build Library
@@ -45,7 +45,7 @@ jobs:
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
- - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
+ - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
with:
node-version-file: '.tool-versions'
- name: Link Utopia-UI in Example App
diff --git a/.github/workflows/test.docs.lib.yml b/.github/workflows/test.docs.lib.yml
index 66065d0c..eea57407 100644
--- a/.github/workflows/test.docs.lib.yml
+++ b/.github/workflows/test.docs.lib.yml
@@ -26,7 +26,7 @@ jobs:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
-# - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
+# - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
# with:
# node-version-file: './.tool-versions'
# - name: Frontend | Build
@@ -42,7 +42,7 @@ jobs:
COVERAGE_REQUIRED: 0
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
- - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
+ - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
with:
node-version-file: '.tool-versions'
- name: Docs
diff --git a/.github/workflows/test.e2e.yml b/.github/workflows/test.e2e.yml
index 2952b2b5..872c521d 100644
--- a/.github/workflows/test.e2e.yml
+++ b/.github/workflows/test.e2e.yml
@@ -15,7 +15,7 @@ jobs:
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5.0.0
- name: Set up Node.js
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v5.0.0
+ uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v5.0.0
with:
node-version-file: ./.tool-versions
cache: 'npm'
@@ -159,7 +159,7 @@ jobs:
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5.0.0
- name: Setup Node.js
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v5.0.0
+ uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v5.0.0
with:
node-version: '20'
cache: 'npm'
diff --git a/.github/workflows/test.lint.cypress.yml b/.github/workflows/test.lint.cypress.yml
index 59363675..bd826140 100644
--- a/.github/workflows/test.lint.cypress.yml
+++ b/.github/workflows/test.lint.cypress.yml
@@ -26,7 +26,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5.0.0
- - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v5.0.0
+ - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v5.0.0
with:
node-version-file: '.tool-versions'
- name: Lint
diff --git a/.github/workflows/test.lint.frontend.yml b/.github/workflows/test.lint.frontend.yml
index 79e7fd13..e86d96ea 100644
--- a/.github/workflows/test.lint.frontend.yml
+++ b/.github/workflows/test.lint.frontend.yml
@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
- - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
+ - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
with:
node-version-file: '.tool-versions'
- name: Lint
diff --git a/.github/workflows/test.lint.lib.examples.yml b/.github/workflows/test.lint.lib.examples.yml
index 1fb853bf..ecbabcce 100644
--- a/.github/workflows/test.lint.lib.examples.yml
+++ b/.github/workflows/test.lint.lib.examples.yml
@@ -25,7 +25,7 @@ jobs:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
-# - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
+# - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
# with:
# node-version-file: './.tool-versions'
# - name: Frontend | Build
@@ -39,7 +39,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
- - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
+ - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
with:
node-version-file: '.tool-versions'
- name: Lint
@@ -53,7 +53,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
- - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
+ - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
with:
node-version-file: '.tool-versions'
- name: Lint
@@ -67,7 +67,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
- - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
+ - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
with:
node-version-file: '.tool-versions'
- name: Lint
@@ -81,7 +81,7 @@ jobs:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
-# - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
+# - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
# with:
# node-version-file: './.tool-versions'
# - name: Frontend | Unit
diff --git a/.github/workflows/test.lint.lib.yml b/.github/workflows/test.lint.lib.yml
index 24e6e957..e66521ef 100644
--- a/.github/workflows/test.lint.lib.yml
+++ b/.github/workflows/test.lint.lib.yml
@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
- - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
+ - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
with:
node-version-file: '.tool-versions'
- name: Lint
diff --git a/.github/workflows/test.lint.pr.yml b/.github/workflows/test.lint.pr.yml
index 61cf293b..95888bd1 100644
--- a/.github/workflows/test.lint.pr.yml
+++ b/.github/workflows/test.lint.pr.yml
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ github.actor != 'dependabot[bot]' }}
steps:
- - uses: amannn/action-semantic-pull-request@v5
+ - uses: amannn/action-semantic-pull-request@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
diff --git a/.github/workflows/test.unit.lib.yml b/.github/workflows/test.unit.lib.yml
index d9c75a9b..11edc747 100644
--- a/.github/workflows/test.unit.lib.yml
+++ b/.github/workflows/test.unit.lib.yml
@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4.1.7
- - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v4.0.3
+ - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4.0.3
with:
node-version-file: '.tool-versions'
- name: Unit
diff --git a/.gitignore b/.gitignore
index 040520f3..5a4915fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,10 @@
.claude/
+app/node_modules/
+lib/node_modules/
data/
node_modules/
cypress/node_modules/
cypress/results/
cypress/runner-results/
cypress/screenshots/
+
diff --git a/app/.eslintignore b/app/.eslintignore
deleted file mode 100644
index 2f3546e5..00000000
--- a/app/.eslintignore
+++ /dev/null
@@ -1,3 +0,0 @@
-node_modules/
-dist/
-data/
\ No newline at end of file
diff --git a/app/.eslintrc.cjs b/app/.eslintrc.cjs
deleted file mode 100644
index af43f19b..00000000
--- a/app/.eslintrc.cjs
+++ /dev/null
@@ -1,223 +0,0 @@
-// eslint-disable-next-line import/no-commonjs
-module.exports = {
- env: {
- browser: true,
- es2021: true,
- },
- extends: [
- 'standard',
- 'eslint:recommended',
- 'plugin:@eslint-community/eslint-comments/recommended',
- 'plugin:@typescript-eslint/recommended',
- 'plugin:import/recommended',
- 'plugin:import/typescript',
- // 'plugin:promise/recommended',
- 'plugin:security/recommended-legacy',
- 'plugin:react/recommended',
- ],
- parserOptions: {
- ecmaVersion: 'latest',
- parser: '@typescript-eslint/parser',
- sourceType: 'module',
- },
- plugins: [
- '@typescript-eslint',
- 'import',
- 'promise',
- 'security',
- 'no-catch-all',
- 'react',
- 'react-hooks',
- 'react-refresh',
- ],
- // TODO also parse this
- ignorePatterns: ['vite.config.ts'],
- settings: {
- 'import/resolver': {
- typescript: true,
- node: {
- extensions: ['.js', '.jsx', '.ts', '.tsx'],
- },
- },
- react: {
- version: '18.2.0',
- },
- },
- rules: {
- 'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
- 'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies
- 'react/react-in-jsx-scope': 'off', // Disable requirement for React import
- 'no-catch-all/no-catch-all': 'error',
- 'no-console': 'error',
- 'no-debugger': 'error',
- camelcase: 'error',
- indent: ['error', 2],
- 'linebreak-style': ['error', 'unix'],
- semi: ['error', 'never'],
- // Optional eslint-comments rule
- '@eslint-community/eslint-comments/no-unused-disable': 'error',
- '@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
- // import
- 'import/export': 'error',
- 'import/no-deprecated': 'error',
- 'import/no-empty-named-blocks': 'error',
- 'import/no-extraneous-dependencies': 'error',
- 'import/no-mutable-exports': 'error',
- 'import/no-unused-modules': 'error',
- 'import/no-named-as-default': 'error',
- 'import/no-named-as-default-member': 'error',
- 'import/no-amd': 'error',
- 'import/no-commonjs': 'error',
- 'import/no-import-module-exports': 'error',
- 'import/no-nodejs-modules': 'off',
- 'import/unambiguous': 'off', // not compatible with scriptless vue files
- 'import/default': 'error',
- 'import/named': 'error',
- 'import/namespace': 'error',
- 'import/no-absolute-path': 'error',
- 'import/no-cycle': 'error',
- 'import/no-dynamic-require': 'error',
- 'import/no-internal-modules': 'off',
- 'import/no-relative-packages': 'error',
- 'import/no-relative-parent-imports': [
- 'error',
- {
- ignore: ['#[src,types,root,components,utils,assets]/*', '@/config/*'],
- },
- ],
- 'import/no-self-import': 'error',
- 'import/no-unresolved': [
- 'error',
- {
- ignore: ['react'],
- },
- ],
- 'import/no-useless-path-segments': 'error',
- 'import/no-webpack-loader-syntax': 'error',
- 'import/consistent-type-specifier-style': 'error',
- 'import/exports-last': 'off',
- 'import/extensions': [
- 'error',
- 'never',
- {
- json: 'always',
- },
- ],
- 'import/first': 'error',
- 'import/group-exports': 'off',
- 'import/newline-after-import': 'error',
- 'import/no-anonymous-default-export': 'off', // todo - consider to enable again
- 'import/no-default-export': 'off', // incompatible with vite & vike
- 'import/no-duplicates': 'error',
- 'import/no-named-default': 'error',
- 'import/no-namespace': 'error',
- 'import/no-unassigned-import': [
- 'error',
- {
- allow: ['**/*.css'],
- },
- ],
- 'import/order': [
- 'error',
- {
- groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
- 'newlines-between': 'always',
- alphabetize: {
- order: 'asc', // sort in ascending order. Options: ["ignore", "asc", "desc"]
- caseInsensitive: true, // ignore case. Options: [true, false]
- },
- distinctGroup: true,
- },
- ],
- 'import/prefer-default-export': 'off',
- // promise
- 'promise/catch-or-return': 'error',
- 'promise/no-return-wrap': 'error',
- 'promise/param-names': 'error',
- 'promise/always-return': 'error',
- 'promise/no-native': 'off',
- 'promise/no-nesting': 'warn',
- 'promise/no-promise-in-callback': 'warn',
- 'promise/no-callback-in-promise': 'warn',
- 'promise/avoid-new': 'warn',
- 'promise/no-new-statics': 'error',
- 'promise/no-return-in-finally': 'warn',
- 'promise/valid-params': 'warn',
- 'promise/prefer-await-to-callbacks': 'error',
- 'promise/no-multiple-resolved': 'error',
- },
- overrides: [
- {
- files: ['*.ts', '*.tsx'],
- parser: '@typescript-eslint/parser',
- parserOptions: {
- tsconfigRootDir: __dirname,
- project: ['./tsconfig.json', '**/tsconfig.json'],
- ecmaVersion: 'latest',
- parser: '@typescript-eslint/parser',
- sourceType: 'module',
- },
- plugins: ['@typescript-eslint'],
- extends: [
- 'plugin:@typescript-eslint/recommended',
- 'plugin:@typescript-eslint/recommended-requiring-type-checking',
- 'plugin:@typescript-eslint/strict',
- ],
- rules: {
- '@typescript-eslint/consistent-type-imports': 'error',
- // allow explicitly defined dangling promises
- '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
- 'no-void': ['error', { allowAsStatement: true }],
- },
- },
- {
- files: ['!*.json'],
- plugins: ['prettier'],
- extends: ['plugin:prettier/recommended'],
- rules: {
- 'prettier/prettier': 'error',
- },
- },
- {
- files: ['*.json'],
- plugins: ['json'],
- extends: ['plugin:json/recommended-with-comments'],
- },
- // {
- // files: ['*.{test,spec}.[tj]s'],
- // plugins: ['vitest'],
- // extends: ['plugin:vitest/all'],
- // rules: {
- // 'vitest/prefer-lowercase-title': 'off',
- // 'vitest/no-hooks': 'off',
- // 'vitest/consistent-test-filename': 'off',
- // 'vitest/prefer-expect-assertions': [
- // 'off',
- // {
- // onlyFunctionsWithExpectInLoop: true,
- // onlyFunctionsWithExpectInCallback: true,
- // },
- // ],
- // 'vitest/prefer-strict-equal': 'off',
- // 'vitest/prefer-to-be-falsy': 'off',
- // 'vitest/prefer-to-be-truthy': 'off',
- // 'vitest/require-hook': [
- // 'error',
- // {
- // allowedFunctionCalls: [
- // 'mockClient.setRequestHandler',
- // 'setActivePinia',
- // 'provideApolloClient',
- // ],
- // },
- // ],
- // },
- // },
- {
- files: ['*.yaml', '*.yml'],
- parser: 'yaml-eslint-parser',
- plugins: ['yml'],
- extends: ['plugin:yml/prettier'],
- },
- ],
-}
diff --git a/app/.prettierrc.json b/app/.prettierrc.json
index 1db2a8cf..72e17590 100644
--- a/app/.prettierrc.json
+++ b/app/.prettierrc.json
@@ -11,4 +11,4 @@
"bracketSameLine": false,
"arrowParens": "always",
"endOfLine": "auto"
-}
\ No newline at end of file
+}
diff --git a/app/eslint.config.js b/app/eslint.config.js
new file mode 100644
index 00000000..805ec2df
--- /dev/null
+++ b/app/eslint.config.js
@@ -0,0 +1,260 @@
+// ESLint v9 flat config for Utopia Map App
+import js from '@eslint/js'
+import eslintCommentsConfigs from '@eslint-community/eslint-plugin-eslint-comments/configs'
+import importXPlugin from 'eslint-plugin-import-x'
+import jsonPlugin from 'eslint-plugin-json'
+import noCatchAllPlugin from 'eslint-plugin-no-catch-all'
+import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
+import promisePlugin from 'eslint-plugin-promise'
+import react from 'eslint-plugin-react'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import securityPlugin from 'eslint-plugin-security'
+import globals from 'globals'
+import tseslint from 'typescript-eslint'
+
+export default tseslint.config(
+ // Ignore patterns
+ {
+ ignores: ['dist/**', 'node_modules/**', 'data/**', 'vite.config.ts'],
+ },
+
+ // Report unused eslint-disable directives (catches stale comments after rule renames)
+ {
+ linterOptions: {
+ reportUnusedDisableDirectives: 'error',
+ },
+ },
+
+ // Base ESLint recommended config
+ js.configs.recommended,
+
+ // ESLint comments recommended config
+ eslintCommentsConfigs.recommended,
+
+ // Security recommended config
+ securityPlugin.configs.recommended,
+
+ // React recommended configs
+ react.configs.flat.recommended,
+ react.configs.flat['jsx-runtime'],
+
+ // Main configuration for JavaScript/TypeScript files
+ {
+ files: ['**/*.{js,jsx,ts,tsx,cjs,mjs}'],
+ languageOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ globals: {
+ ...globals.browser,
+ ...globals.es2021,
+ },
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ plugins: {
+ react: react,
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ 'import-x': importXPlugin,
+ promise: promisePlugin,
+ 'no-catch-all': noCatchAllPlugin,
+ },
+ settings: {
+ react: {
+ version: '18.2.0',
+ },
+ 'import-x/resolver': {
+ typescript: true,
+ node: {
+ extensions: ['.js', '.jsx', '.ts', '.tsx'],
+ },
+ },
+ },
+ rules: {
+ // ESLint comments rules - allow whole-file disables without eslint-enable
+ '@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
+
+ // Basic rules
+ 'no-console': 'error',
+ 'no-debugger': 'error',
+ camelcase: 'error',
+
+ // Standard JS rules (replacing eslint-config-standard)
+ semi: ['error', 'never'],
+ quotes: ['error', 'single', { avoidEscape: true }],
+ 'comma-dangle': ['error', 'always-multiline'],
+ // Disabled: conflicts with common TypeScript/React patterns
+ // 'space-before-function-paren': ['error', 'always'],
+ 'keyword-spacing': ['error', { before: true, after: true }],
+ 'space-infix-ops': 'error',
+ 'eol-last': ['error', 'always'],
+ 'no-trailing-spaces': 'error',
+ 'object-curly-spacing': ['error', 'always'],
+ 'array-bracket-spacing': ['error', 'never'],
+ 'computed-property-spacing': ['error', 'never'],
+ 'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }],
+ // Disable indent rule due to known issues with TypeScript/JSX
+ // 'indent': ['error', 2],
+ 'linebreak-style': ['error', 'unix'],
+
+ // Additional standard rules that were missing
+ eqeqeq: ['error', 'always', { null: 'ignore' }],
+ 'new-cap': ['error', { newIsCap: true, capIsNew: false, properties: true }],
+ 'array-callback-return': ['error', { allowImplicit: false, checkForEach: false }],
+
+ // React rules
+ 'react/react-in-jsx-scope': 'off',
+ 'react/no-unescaped-entities': 'error',
+ 'react-hooks/rules-of-hooks': 'error',
+ 'react-hooks/exhaustive-deps': 'warn',
+ 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
+
+ // Import rules
+ 'import-x/export': 'error',
+ 'import-x/no-deprecated': 'error',
+ 'import-x/no-empty-named-blocks': 'error',
+ 'import-x/no-extraneous-dependencies': 'error',
+ 'import-x/no-mutable-exports': 'error',
+ 'import-x/no-named-as-default': 'error',
+ 'import-x/no-named-as-default-member': 'error',
+ 'import-x/no-amd': 'error',
+ 'import-x/no-commonjs': 'error',
+ 'import-x/no-nodejs-modules': 'off',
+ 'import-x/default': 'error',
+ 'import-x/named': 'error',
+ 'import-x/namespace': 'error',
+ 'import-x/no-absolute-path': 'error',
+ 'import-x/no-cycle': 'error',
+ 'import-x/no-dynamic-require': 'error',
+ 'import-x/no-internal-modules': 'off',
+ 'import-x/no-relative-packages': 'error',
+ 'import-x/no-self-import': 'error',
+ 'import-x/no-unresolved': ['error', { ignore: ['react'] }],
+ 'import-x/no-useless-path-segments': 'error',
+ 'import-x/no-webpack-loader-syntax': 'error',
+ 'import-x/consistent-type-specifier-style': 'error',
+ 'import-x/exports-last': 'off',
+ 'import-x/extensions': ['error', 'never', { json: 'always' }],
+ 'import-x/first': 'error',
+ 'import-x/group-exports': 'off',
+ 'import-x/newline-after-import': 'error',
+ 'import-x/no-anonymous-default-export': 'off',
+ 'import-x/no-default-export': 'off',
+ 'import-x/no-duplicates': 'error',
+ 'import-x/no-named-default': 'error',
+ 'import-x/no-namespace': 'error',
+ 'import-x/no-unassigned-import': ['error', { allow: ['**/*.css'] }],
+ 'import-x/order': [
+ 'error',
+ {
+ groups: [
+ 'builtin',
+ 'external',
+ 'internal',
+ 'parent',
+ 'sibling',
+ 'index',
+ 'object',
+ 'type',
+ ],
+ 'newlines-between': 'always',
+ alphabetize: {
+ order: 'asc',
+ caseInsensitive: true,
+ },
+ distinctGroup: true,
+ },
+ ],
+ 'import-x/prefer-default-export': 'off',
+
+ // Promise rules
+ 'promise/catch-or-return': 'error',
+ 'promise/no-return-wrap': 'error',
+ 'promise/param-names': 'error',
+ 'promise/always-return': 'error',
+ 'promise/no-native': 'off',
+ 'promise/no-nesting': 'warn',
+ 'promise/no-promise-in-callback': 'warn',
+ 'promise/no-callback-in-promise': 'warn',
+ 'promise/avoid-new': 'warn',
+ 'promise/no-new-statics': 'error',
+ 'promise/no-return-in-finally': 'warn',
+ 'promise/valid-params': 'warn',
+ 'promise/prefer-await-to-callbacks': 'error',
+ 'promise/no-multiple-resolved': 'error',
+
+ // Security and other rules
+ 'no-catch-all/no-catch-all': 'error',
+
+ // Additional import rules
+ 'import-x/no-unused-modules': 'error',
+ 'import-x/no-import-module-exports': 'error',
+ 'import-x/unambiguous': 'off',
+ 'import-x/no-relative-parent-imports': [
+ 'error',
+ {
+ ignore: ['#[src,types,root,components,utils,assets]/*', '@/config/*'],
+ },
+ ],
+ },
+ },
+
+ // TypeScript configs (applied after main config)
+ ...tseslint.configs.recommended,
+ ...tseslint.configs.strict,
+ ...tseslint.configs.stylistic,
+
+ // TypeScript type-checking configuration
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ ...tseslint.configs.recommendedTypeChecked,
+ ...tseslint.configs.strictTypeChecked,
+ ...tseslint.configs.stylisticTypeChecked,
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: ['./tsconfig.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+ rules: {
+ '@typescript-eslint/consistent-type-imports': 'error',
+ '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
+ 'no-void': ['error', { allowAsStatement: true }],
+
+ // Disable empty function rule - legitimate use in React contexts and empty constructors
+ '@typescript-eslint/no-empty-function': 'off',
+
+ // Configure no-unused-expressions to allow logical AND and ternary patterns
+ '@typescript-eslint/no-unused-expressions': [
+ 'error',
+ {
+ allowShortCircuit: true,
+ allowTernary: true,
+ },
+ ],
+ },
+ },
+
+ // JSON files configuration
+ {
+ files: ['**/*.json'],
+ plugins: {
+ json: jsonPlugin,
+ },
+ rules: {
+ // Disable TypeScript-specific rules for JSON files
+ '@typescript-eslint/no-unused-expressions': 'off',
+ // JSON-specific rules
+ 'json/*': 'error',
+ },
+ },
+
+ // Prettier recommended config (should be last to override other formatting rules)
+ eslintPluginPrettierRecommended,
+)
diff --git a/app/package.json b/app/package.json
index 6615495a..4cdac8b8 100644
--- a/app/package.json
+++ b/app/package.json
@@ -9,50 +9,47 @@
"scripts": {
"dev": "vite --host",
"build": "tsc && vite build",
- "test:lint:eslint": "eslint --ext .ts,.tsx,.js,.jsx,.cjs,.mjs,.json,.yml,.yaml --max-warnings 0 .",
+ "test:lint:eslint": "eslint --max-warnings 0 .",
"preview": "vite preview"
},
"dependencies": {
"@directus/sdk": "^17.0.2",
"@heroicons/react": "^2.1.1",
- "@tailwindcss/vite": "^4.1.17",
+ "@tailwindcss/vite": "^4.1.18",
"@types/geojson": "^7946.0.10",
"axios": "^1.13.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-rnd": "^10.4.1",
"react-router-dom": "^6.23.0",
- "vite-tsconfig-paths": "^5.1.4",
- "utopia-ui": "^3.0.111"
+ "utopia-ui": "^3.0.111",
+ "vite-tsconfig-paths": "^5.1.4"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "^4.4.1",
- "@types/node": "^24.10.1",
+ "@eslint/js": "^9.36.0",
+ "@types/node": "^24.10.2",
"@types/react": "^18.2.79",
"@types/react-dom": "^18.2.25",
- "@typescript-eslint/eslint-plugin": "^5.62.0",
- "@typescript-eslint/parser": "^5.62.0",
"@vitejs/plugin-react": "^4.0.0",
- "daisyui": "^5.5.5",
- "eslint": "^8.24.0",
- "eslint-config-prettier": "^10.1.8",
- "eslint-config-standard": "^17.1.0",
+ "daisyui": "^5.5.14",
+ "eslint": "^9.39.2",
"eslint-import-resolver-typescript": "^4.4.4",
- "eslint-plugin-import": "^2.31.0",
- "eslint-plugin-json": "^3.1.0",
- "eslint-plugin-n": "^17.23.1",
+ "eslint-plugin-import-x": "^4.16.1",
+ "eslint-plugin-json": "^4.0.1",
"eslint-plugin-no-catch-all": "^1.1.0",
- "eslint-plugin-prettier": "^5.2.1",
+ "eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-promise": "^7.2.1",
"eslint-plugin-react": "^7.31.8",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"eslint-plugin-security": "^3.0.1",
- "eslint-plugin-yml": "^1.14.0",
+ "globals": "^16.3.0",
"postcss": "^8.4.30",
- "tailwindcss": "^4.1.17",
+ "tailwindcss": "^4.1.18",
"typescript": "^5.9.3",
- "vite": "^7.2.4",
- "vite-plugin-pwa": "^1.1.0"
+ "typescript-eslint": "^8.9.0",
+ "vite": "^7.3.0",
+ "vite-plugin-pwa": "^1.2.0"
}
}
diff --git a/app/src/App.tsx b/app/src/App.tsx
index 2c75a8fe..d508c5f8 100644
--- a/app/src/App.tsx
+++ b/app/src/App.tsx
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
-/* eslint-disable import/order */
+/* eslint-disable import-x/order */
/* eslint-disable eqeqeq */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable react-hooks/exhaustive-deps */
@@ -8,6 +8,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable new-cap */
/* eslint-disable @typescript-eslint/prefer-optional-chain */
+/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
+/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
diff --git a/app/src/ModalContent.tsx b/app/src/ModalContent.tsx
index f501ddab..c79a850b 100644
--- a/app/src/ModalContent.tsx
+++ b/app/src/ModalContent.tsx
@@ -2,6 +2,7 @@
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
+/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
import { useEffect, useState } from 'react'
import { TextView } from 'utopia-ui'
@@ -22,7 +23,9 @@ export function Welcome1({ clickAction1, map }: ChapterProps) {
@@ -45,7 +48,9 @@ export function Welcome1({ clickAction1, map }: ChapterProps) {
diff --git a/app/src/api/directus.ts b/app/src/api/directus.ts
index 140ca4ba..2e28ed01 100644
--- a/app/src/api/directus.ts
+++ b/app/src/api/directus.ts
@@ -4,7 +4,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
import { createDirectus, rest, authentication } from '@directus/sdk'
-// eslint-disable-next-line import/no-relative-parent-imports
+// eslint-disable-next-line import-x/no-relative-parent-imports
import { config } from '../config'
import type { AuthenticationData, AuthenticationStorage } from '@directus/sdk'
@@ -86,9 +86,10 @@ export const authLocalStorage = (mainKey = 'directus_storage') =>
// implementation of set, here set the value at mainKey in localStorage, or remove it if value is null
set: async (value: AuthenticationData | null) => {
if (!value) {
- return window.localStorage.removeItem(mainKey)
+ window.localStorage.removeItem(mainKey)
+ return
}
- return window.localStorage.setItem(mainKey, JSON.stringify(value))
+ window.localStorage.setItem(mainKey, JSON.stringify(value))
},
}) as AuthenticationStorage
diff --git a/app/src/api/inviteApi.ts b/app/src/api/inviteApi.ts
index 7a6d2c24..8dcd8d8a 100644
--- a/app/src/api/inviteApi.ts
+++ b/app/src/api/inviteApi.ts
@@ -1,4 +1,3 @@
-/* @eslint-disable-next-line import/no-relative-parent-imports */
import { config } from '@/config'
import type { UserApi } from 'utopia-ui'
diff --git a/app/src/api/itemsApi.ts b/app/src/api/itemsApi.ts
index 796018a5..7006f96b 100644
--- a/app/src/api/itemsApi.ts
+++ b/app/src/api/itemsApi.ts
@@ -20,8 +20,8 @@ export class itemsApi
implements FullItemsApi {
constructor(
collectionName: keyof MyCollections,
- layerId?: string | undefined,
- mapId?: string | undefined,
+ layerId?: string,
+ mapId?: string,
filter?: any,
customParameter?: any,
) {
@@ -111,8 +111,8 @@ export class itemsApi implements FullItemsApi {
async deleteItem(id: string): Promise {
try {
- const result = await directusClient.request(deleteItem(this.collectionName, id))
- return result as unknown as boolean
+ await directusClient.request(deleteItem(this.collectionName, id))
+ return true
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message
diff --git a/app/src/api/layersApi.ts b/app/src/api/layersApi.ts
index b91e5881..fa779ecf 100644
--- a/app/src/api/layersApi.ts
+++ b/app/src/api/layersApi.ts
@@ -21,6 +21,7 @@ export class layersApi {
{ itemType: ['*.*', { profileTemplate: ['*', 'item.*.*.*.*'] }] },
{ markerIcon: ['*'] } as any,
],
+ // eslint-disable-next-line camelcase
filter: { maps: { maps_id: { id: { _eq: this.mapId } } } },
limit: 500,
sort: ['sort'],
diff --git a/app/src/api/permissionsApi.ts b/app/src/api/permissionsApi.ts
index c0f42647..ed1c027e 100644
--- a/app/src/api/permissionsApi.ts
+++ b/app/src/api/permissionsApi.ts
@@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-useless-constructor */
-/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */
import { readPermissions } from '@directus/sdk'
diff --git a/app/src/api/refiBcnApi.ts b/app/src/api/refiBcnApi.ts
index 5f685ed8..65717ad3 100644
--- a/app/src/api/refiBcnApi.ts
+++ b/app/src/api/refiBcnApi.ts
@@ -1,7 +1,7 @@
/* eslint-disable no-console */
-/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-explicit-any */
+/* eslint-disable @typescript-eslint/no-unsafe-return */
import axios from 'axios'
import type { ItemsApi } from 'utopia-ui'
diff --git a/app/src/api/userApi.ts b/app/src/api/userApi.ts
index c63e1baa..ff8e7744 100644
--- a/app/src/api/userApi.ts
+++ b/app/src/api/userApi.ts
@@ -3,7 +3,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-explicit-any */
-/* eslint-disable @typescript-eslint/no-unsafe-return */
import { createUser, passwordRequest, passwordReset, readMe, updateMe } from '@directus/sdk'
import { directusClient } from './directus'
@@ -54,7 +53,8 @@ export class UserApi {
async logout(): Promise {
try {
- return await directusClient.logout()
+ await directusClient.logout()
+ return
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message
@@ -98,7 +98,8 @@ export class UserApi {
async requestPasswordReset(email: string, reset_url?: string): Promise {
try {
- return await directusClient.request(passwordRequest(email, reset_url))
+ await directusClient.request(passwordRequest(email, reset_url))
+ return
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message
@@ -108,7 +109,8 @@ export class UserApi {
async passwordReset(reset_token: string, new_password: string): Promise {
try {
- return await directusClient.request(passwordReset(reset_token, new_password))
+ await directusClient.request(passwordReset(reset_token, new_password))
+ return
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message
diff --git a/app/src/main.tsx b/app/src/main.tsx
index 86140f18..ccb66470 100644
--- a/app/src/main.tsx
+++ b/app/src/main.tsx
@@ -1,5 +1,4 @@
-/* eslint-disable import/extensions */
-/* eslint-disable import/no-named-as-default-member */
+/* eslint-disable import-x/extensions */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import React from 'react'
import ReactDOM from 'react-dom/client'
diff --git a/app/src/pages/Landingpage.tsx b/app/src/pages/Landingpage.tsx
index a5d221fa..97c355c0 100644
--- a/app/src/pages/Landingpage.tsx
+++ b/app/src/pages/Landingpage.tsx
@@ -1,7 +1,7 @@
/* eslint-disable react/no-unescaped-entities */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
-/* eslint-disable import/no-relative-parent-imports */
+/* eslint-disable import-x/no-relative-parent-imports */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable new-cap */
diff --git a/app/src/pages/MapContainer.tsx b/app/src/pages/MapContainer.tsx
index 0d305d3d..f0b0cd9f 100644
--- a/app/src/pages/MapContainer.tsx
+++ b/app/src/pages/MapContainer.tsx
@@ -1,9 +1,9 @@
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/no-explicit-any */
-/* eslint-disable import/no-relative-parent-imports */
+/* eslint-disable import-x/no-relative-parent-imports */
/* eslint-disable new-cap */
-/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
+/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { useEffect, useState } from 'react'
import {
diff --git a/app/src/pages/data.ts b/app/src/pages/data.ts
index f72437dc..cd36c415 100644
--- a/app/src/pages/data.ts
+++ b/app/src/pages/data.ts
@@ -1,3 +1,4 @@
+/* eslint-disable camelcase */
import type { Item, Tag } from 'utopia-ui'
export const tags: Tag[] = [
diff --git a/app/tsconfig.json b/app/tsconfig.json
index 95a0cf49..784becfb 100644
--- a/app/tsconfig.json
+++ b/app/tsconfig.json
@@ -12,35 +12,17 @@
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
- "@/*": [
- "src/*"
- ],
- "utopia-ui": [
- "../lib/src"
- ],
- "#components/*": [
- "../lib/src/Components/*"
- ],
- "#utils/*": [
- "../lib/src/Utils/*"
- ],
- "#types/*": [
- "../lib/src/types/*"
- ],
- "#assets/*": [
- "../lib/src/assets/*"
- ],
- "#src/*": [
- "../lib/src/*"
- ],
- "#root/*": [
- "../lib/*"
- ]
+ "@/*": ["src/*"],
+ "utopia-ui": ["../lib/src"],
+ "#components/*": ["../lib/src/Components/*"],
+ "#utils/*": ["../lib/src/Utils/*"],
+ "#types/*": ["../lib/src/types/*"],
+ "#assets/*": ["../lib/src/assets/*"],
+ "#src/*": ["../lib/src/*"],
+ "#root/*": ["../lib/*"]
}
},
- "include": [
- "src"
- ],
+ "include": ["src"],
"references": [
{
"path": "./tsconfig.node.json"
diff --git a/backend/prepare-seed.js b/backend/prepare-seed.js
new file mode 100644
index 00000000..94f025df
--- /dev/null
+++ b/backend/prepare-seed.js
@@ -0,0 +1,99 @@
+#!/usr/bin/env node
+
+/**
+ * Prepares seed data by updating event dates relative to the current date.
+ *
+ * This script modifies items.json in-place, updating all items with
+ * layer "layer-events" to have realistic start/end dates that ensure
+ * events are visible in the app (end dates in the future).
+ *
+ * Date strategy:
+ * - Event 1 (item-event-1): Long-running, started 30 days ago, ends in 365 days
+ * - Event 2 (item-event-2): Upcoming single-day event in 7 days
+ * - Event 3 (item-event-3): Ongoing event, started yesterday, ends tomorrow
+ * - Event 4 (item-event-4): Upcoming multi-day conference in 30 days
+ */
+
+const fs = require('fs')
+const path = require('path')
+
+const seedPath = path.join(__dirname, 'directus-config/development/seed/items.json')
+
+function addDays(date, days) {
+ const result = new Date(date)
+ result.setDate(result.getDate() + days)
+ return result
+}
+
+function addHours(date, hours) {
+ const result = new Date(date)
+ result.setHours(result.getHours() + hours)
+ return result
+}
+
+function formatDateTime(date) {
+ return date.toISOString().slice(0, 19)
+}
+
+/**
+ * Calculate dynamic dates for each event based on current time
+ */
+function getEventDates(syncId, now) {
+ const dateConfigs = {
+ // "Some Event" - long-running, started in past, ends far in future
+ 'item-event-1': {
+ start: addDays(now, -30),
+ end: addDays(now, 365)
+ },
+ // "Tech Meetup Munich" - upcoming single-day event (the one used in search tests)
+ 'item-event-2': {
+ start: addHours(addDays(now, 7), 18),
+ end: addHours(addDays(now, 7), 21)
+ },
+ // "Sustainability Workshop NYC" - ongoing event (started yesterday, ends tomorrow)
+ 'item-event-3': {
+ start: addHours(addDays(now, -1), 14),
+ end: addHours(addDays(now, 1), 17)
+ },
+ // "Open Source Conference" - upcoming multi-day conference
+ 'item-event-4': {
+ start: addHours(addDays(now, 30), 9),
+ end: addHours(addDays(now, 32), 18)
+ }
+ }
+
+ return dateConfigs[syncId] || null
+}
+
+function prepareSeedData() {
+ // Read the current items.json
+ const content = fs.readFileSync(seedPath, 'utf8')
+ const seedData = JSON.parse(content)
+
+ const now = new Date()
+ let updatedCount = 0
+
+ // Update event items with dynamic dates
+ for (const item of seedData.data) {
+ if (item.layer === 'layer-events' && item._sync_id) {
+ const dates = getEventDates(item._sync_id, now)
+ if (dates) {
+ item.start = formatDateTime(dates.start)
+ item.end = formatDateTime(dates.end)
+ console.log(` ${item._sync_id} (${item.name}):`)
+ console.log(` start: ${item.start}`)
+ console.log(` end: ${item.end}`)
+ updatedCount++
+ }
+ }
+ }
+
+ // Write back to items.json
+ fs.writeFileSync(seedPath, JSON.stringify(seedData, null, 4))
+
+ console.log(`\nUpdated ${updatedCount} event(s) with dynamic dates.`)
+}
+
+console.log('Preparing seed data with dynamic dates...\n')
+prepareSeedData()
+
diff --git a/backend/seed.sh b/backend/seed.sh
index b62384f6..52811ed7 100755
--- a/backend/seed.sh
+++ b/backend/seed.sh
@@ -15,6 +15,9 @@ PGDATABASE="${PGDATABASE:-'directus'}"
PROJECT_NAME="${PROJECT:-development}"
PROJECT_FOLDER=$SCRIPT_DIR/directus-config/$PROJECT_NAME
+echo "Preparing seed data with dynamic dates"
+node $SCRIPT_DIR/prepare-seed.js || exit 1
+
echo "Seed data"
npx directus-sync@3.4.0 seed push \
--seed-path $PROJECT_FOLDER/seed \
diff --git a/cypress/e2e/search/search-flows.cy.ts b/cypress/e2e/search/search-flows.cy.ts
index 9b24b84e..fc16a276 100644
--- a/cypress/e2e/search/search-flows.cy.ts
+++ b/cypress/e2e/search/search-flows.cy.ts
@@ -74,7 +74,7 @@ describe('Utopia Map Search', () => {
cy.contains('Wat Arun').first().click()
})
- cy.get('.leaflet-popup').should('be.visible')
+ cy.get('.leaflet-popup', { timeout: 15000 }).should('exist')
cy.get('.leaflet-popup-content').should('contain', 'Wat Arun')
})
diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts
index 72832e81..10d7bd52 100644
--- a/cypress/support/commands.ts
+++ b/cypress/support/commands.ts
@@ -76,6 +76,12 @@ Cypress.Commands.add('searchFor', (query: string) => {
Cypress.Commands.add('waitForMapReady', () => {
cy.get('[data-cy="search-input"]', { timeout: 10000 }).should('be.visible')
cy.get('.leaflet-container', { timeout: 10000 }).should('be.visible')
+ cy.wait('@getLayers', { timeout: 15000 }).then((interception) => {
+ const layerCount = interception.response?.body?.data?.length || 3
+ for (let i = 0; i < layerCount; i++) {
+ cy.wait('@getLayerItems', { timeout: 15000 })
+ }
+ })
cy.get('.leaflet-marker-icon', { timeout: 15000 }).should('have.length.at.least', 1)
})
diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts
index fc990283..cafcadf7 100644
--- a/cypress/support/e2e.ts
+++ b/cypress/support/e2e.ts
@@ -5,6 +5,71 @@ import './commands'
// for screenshot embedding
import addContext from 'mochawesome/addContext'
+const photonMockData: Record = {
+ berlin: {
+ type: 'FeatureCollection',
+ features: [
+ {
+ type: 'Feature',
+ properties: {
+ osm_type: 'R',
+ osm_id: 62422,
+ osm_key: 'place',
+ osm_value: 'city',
+ type: 'city',
+ countrycode: 'DE',
+ name: 'Berlin',
+ country: 'Germany',
+ state: 'Berlin',
+ extent: [13.088345, 52.6755087, 13.7611609, 52.3382448],
+ },
+ geometry: { type: 'Point', coordinates: [13.3951309, 52.5173885] },
+ },
+ ],
+ },
+ 'wat arun': {
+ type: 'FeatureCollection',
+ features: [
+ {
+ type: 'Feature',
+ properties: {
+ osm_type: 'W',
+ osm_id: 25867629,
+ osm_key: 'tourism',
+ osm_value: 'attraction',
+ type: 'attraction',
+ countrycode: 'TH',
+ name: 'Wat Arun',
+ country: 'Thailand',
+ city: 'Bangkok',
+ extent: [100.4882, 13.7437, 100.4912, 13.7407],
+ },
+ geometry: { type: 'Point', coordinates: [100.4897, 13.7437] },
+ },
+ ],
+ },
+}
+
+beforeEach(() => {
+ cy.intercept('GET', 'https://photon.komoot.io/api/*', (req) => {
+ const url = new URL(req.url)
+ const query = (url.searchParams.get('q') || '').toLowerCase()
+
+ const mockKey = Object.keys(photonMockData).find((key) =>
+ query.includes(key.toLowerCase()),
+ )
+
+ if (mockKey) {
+ req.reply(photonMockData[mockKey])
+ } else {
+ req.reply({ type: 'FeatureCollection', features: [] })
+ }
+ }).as('photonApi')
+
+ cy.intercept('GET', '**/items/layers*').as('getLayers')
+ cy.intercept('GET', '**/items/items*').as('getLayerItems')
+})
+
// Global exception handler
Cypress.on('uncaught:exception', (err) => {
// eslint-disable-next-line no-console
diff --git a/lib/.eslintignore b/lib/.eslintignore
deleted file mode 100644
index c24c07ac..00000000
--- a/lib/.eslintignore
+++ /dev/null
@@ -1,5 +0,0 @@
-node_modules/
-dist/
-examples/
-docs/
-coverage/
\ No newline at end of file
diff --git a/lib/.eslintrc.cjs b/lib/.eslintrc.cjs
deleted file mode 100644
index e4a3299b..00000000
--- a/lib/.eslintrc.cjs
+++ /dev/null
@@ -1,221 +0,0 @@
-// eslint-disable-next-line import/no-commonjs
-module.exports = {
- env: {
- browser: true,
- es2021: true,
- },
- extends: [
- 'standard',
- 'eslint:recommended',
- 'plugin:@eslint-community/eslint-comments/recommended',
- 'plugin:@typescript-eslint/recommended',
- 'plugin:import/recommended',
- 'plugin:import/typescript',
- // 'plugin:promise/recommended',
- 'plugin:security/recommended-legacy',
- 'plugin:react/recommended',
- ],
- parserOptions: {
- ecmaVersion: 'latest',
- parser: '@typescript-eslint/parser',
- sourceType: 'module',
- },
- plugins: [
- '@typescript-eslint',
- 'import',
- 'promise',
- 'security',
- 'no-catch-all',
- 'react',
- 'react-hooks',
- 'react-refresh',
- ],
- settings: {
- 'import/resolver': {
- typescript: true,
- node: {
- extensions: ['.js', '.jsx', '.ts', '.tsx'],
- },
- },
- react: {
- version: '18.2.0',
- },
- },
- rules: {
- 'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
- 'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies
- 'react/react-in-jsx-scope': 'off', // Disable requirement for React import
- 'no-catch-all/no-catch-all': 'error',
- 'no-console': 'error',
- 'no-debugger': 'error',
- camelcase: 'error',
- indent: ['error', 2],
- 'linebreak-style': ['error', 'unix'],
- semi: ['error', 'never'],
- // Optional eslint-comments rule
- '@eslint-community/eslint-comments/no-unused-disable': 'error',
- '@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
- // import
- 'import/export': 'error',
- 'import/no-deprecated': 'error',
- 'import/no-empty-named-blocks': 'error',
- 'import/no-extraneous-dependencies': 'error',
- 'import/no-mutable-exports': 'error',
- 'import/no-unused-modules': 'error',
- 'import/no-named-as-default': 'error',
- 'import/no-named-as-default-member': 'error',
- 'import/no-amd': 'error',
- 'import/no-commonjs': 'error',
- 'import/no-import-module-exports': 'error',
- 'import/no-nodejs-modules': 'off',
- 'import/unambiguous': 'off', // not compatible with scriptless vue files
- 'import/default': 'error',
- 'import/named': 'error',
- 'import/namespace': 'error',
- 'import/no-absolute-path': 'error',
- 'import/no-cycle': 'error',
- 'import/no-dynamic-require': 'error',
- 'import/no-internal-modules': 'off',
- 'import/no-relative-packages': 'error',
- 'import/no-relative-parent-imports': [
- 'error',
- {
- ignore: ['#[src,types,root,components,utils,assets]/*'],
- },
- ],
- 'import/no-self-import': 'error',
- 'import/no-unresolved': [
- 'error',
- {
- ignore: ['react'],
- },
- ],
- 'import/no-useless-path-segments': 'error',
- 'import/no-webpack-loader-syntax': 'error',
- 'import/consistent-type-specifier-style': 'error',
- 'import/exports-last': 'off',
- 'import/extensions': [
- 'error',
- 'never',
- {
- json: 'always',
- },
- ],
- 'import/first': 'error',
- 'import/group-exports': 'off',
- 'import/newline-after-import': 'error',
- 'import/no-anonymous-default-export': 'off', // todo - consider to enable again
- 'import/no-default-export': 'off', // incompatible with vite & vike
- 'import/no-duplicates': 'error',
- 'import/no-named-default': 'error',
- 'import/no-namespace': 'error',
- 'import/no-unassigned-import': [
- 'error',
- {
- allow: ['**/*.css'],
- },
- ],
- 'import/order': [
- 'error',
- {
- groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
- 'newlines-between': 'always',
- alphabetize: {
- order: 'asc', // sort in ascending order. Options: ["ignore", "asc", "desc"]
- caseInsensitive: true, // ignore case. Options: [true, false]
- },
- distinctGroup: true,
- },
- ],
- 'import/prefer-default-export': 'off',
- // promise
- 'promise/catch-or-return': 'error',
- 'promise/no-return-wrap': 'error',
- 'promise/param-names': 'error',
- 'promise/always-return': 'error',
- 'promise/no-native': 'off',
- 'promise/no-nesting': 'warn',
- 'promise/no-promise-in-callback': 'warn',
- 'promise/no-callback-in-promise': 'warn',
- 'promise/avoid-new': 'warn',
- 'promise/no-new-statics': 'error',
- 'promise/no-return-in-finally': 'warn',
- 'promise/valid-params': 'warn',
- 'promise/prefer-await-to-callbacks': 'error',
- 'promise/no-multiple-resolved': 'error',
- },
- overrides: [
- {
- files: ['*.ts', '*.tsx'],
- parser: '@typescript-eslint/parser',
- parserOptions: {
- tsconfigRootDir: __dirname,
- project: ['./tsconfig.json', '**/tsconfig.json'],
- ecmaVersion: 'latest',
- parser: '@typescript-eslint/parser',
- sourceType: 'module',
- },
- plugins: ['@typescript-eslint'],
- extends: [
- 'plugin:@typescript-eslint/recommended',
- 'plugin:@typescript-eslint/recommended-requiring-type-checking',
- 'plugin:@typescript-eslint/strict',
- ],
- rules: {
- '@typescript-eslint/consistent-type-imports': 'error',
- // allow explicitly defined dangling promises
- '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
- 'no-void': ['error', { allowAsStatement: true }],
- },
- },
- {
- files: ['!*.json'],
- plugins: ['prettier'],
- extends: ['plugin:prettier/recommended'],
- rules: {
- 'prettier/prettier': 'error',
- },
- },
- {
- files: ['*.json'],
- plugins: ['json'],
- extends: ['plugin:json/recommended-with-comments'],
- },
- // {
- // files: ['*.{test,spec}.[tj]s'],
- // plugins: ['vitest'],
- // extends: ['plugin:vitest/all'],
- // rules: {
- // 'vitest/prefer-lowercase-title': 'off',
- // 'vitest/no-hooks': 'off',
- // 'vitest/consistent-test-filename': 'off',
- // 'vitest/prefer-expect-assertions': [
- // 'off',
- // {
- // onlyFunctionsWithExpectInLoop: true,
- // onlyFunctionsWithExpectInCallback: true,
- // },
- // ],
- // 'vitest/prefer-strict-equal': 'off',
- // 'vitest/prefer-to-be-falsy': 'off',
- // 'vitest/prefer-to-be-truthy': 'off',
- // 'vitest/require-hook': [
- // 'error',
- // {
- // allowedFunctionCalls: [
- // 'mockClient.setRequestHandler',
- // 'setActivePinia',
- // 'provideApolloClient',
- // ],
- // },
- // ],
- // },
- // },
- {
- files: ['*.yaml', '*.yml'],
- parser: 'yaml-eslint-parser',
- plugins: ['yml'],
- extends: ['plugin:yml/prettier'],
- },
- ],
-}
diff --git a/lib/.prettierrc.json b/lib/.prettierrc.json
index 1db2a8cf..72e17590 100644
--- a/lib/.prettierrc.json
+++ b/lib/.prettierrc.json
@@ -11,4 +11,4 @@
"bracketSameLine": false,
"arrowParens": "always",
"endOfLine": "auto"
-}
\ No newline at end of file
+}
diff --git a/lib/cypress/support/component.ts b/lib/cypress/support/component.ts
index 13ea7ddf..86f1ddf0 100644
--- a/lib/cypress/support/component.ts
+++ b/lib/cypress/support/component.ts
@@ -14,7 +14,7 @@
// ***********************************************************
// Import commands.js using ES2015 syntax:
-// eslint-disable-next-line import/no-unassigned-import
+// eslint-disable-next-line import-x/no-unassigned-import
import './commands'
import { mount } from 'cypress/react'
diff --git a/lib/eslint.config.js b/lib/eslint.config.js
new file mode 100644
index 00000000..855a03ad
--- /dev/null
+++ b/lib/eslint.config.js
@@ -0,0 +1,260 @@
+// ESLint v9 flat config for Utopia UI Library
+import js from '@eslint/js'
+import eslintCommentsConfigs from '@eslint-community/eslint-plugin-eslint-comments/configs'
+import importXPlugin from 'eslint-plugin-import-x'
+import jsonPlugin from 'eslint-plugin-json'
+import noCatchAllPlugin from 'eslint-plugin-no-catch-all'
+import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
+import promisePlugin from 'eslint-plugin-promise'
+import react from 'eslint-plugin-react'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import securityPlugin from 'eslint-plugin-security'
+import globals from 'globals'
+import tseslint from 'typescript-eslint'
+
+export default tseslint.config(
+ // Ignore patterns
+ {
+ ignores: ['dist/**', 'node_modules/**', 'coverage/**', 'docs/**', 'examples/**'],
+ },
+
+ // Report unused eslint-disable directives (catches stale comments after rule renames)
+ {
+ linterOptions: {
+ reportUnusedDisableDirectives: 'error',
+ },
+ },
+
+ // Base ESLint recommended config
+ js.configs.recommended,
+
+ // ESLint comments recommended config
+ eslintCommentsConfigs.recommended,
+
+ // Security recommended config
+ securityPlugin.configs.recommended,
+
+ // React recommended configs
+ react.configs.flat.recommended,
+ react.configs.flat['jsx-runtime'],
+
+ // Main configuration for JavaScript/TypeScript files
+ {
+ files: ['**/*.{js,jsx,ts,tsx,cjs,mjs}'],
+ languageOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ globals: {
+ ...globals.browser,
+ ...globals.es2021,
+ },
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ },
+ plugins: {
+ react: react,
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ 'import-x': importXPlugin,
+ promise: promisePlugin,
+ 'no-catch-all': noCatchAllPlugin,
+ },
+ settings: {
+ react: {
+ version: '18.2.0',
+ },
+ 'import-x/resolver': {
+ typescript: true,
+ node: {
+ extensions: ['.js', '.jsx', '.ts', '.tsx'],
+ },
+ },
+ },
+ rules: {
+ // ESLint comments rules - allow whole-file disables without eslint-enable
+ '@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
+
+ // Basic rules
+ 'no-console': 'error',
+ 'no-debugger': 'error',
+ camelcase: 'error',
+
+ // Standard JS rules
+ semi: ['error', 'never'],
+ quotes: ['error', 'single', { avoidEscape: true }],
+ 'comma-dangle': ['error', 'always-multiline'],
+ 'keyword-spacing': ['error', { before: true, after: true }],
+ 'space-infix-ops': 'error',
+ 'eol-last': ['error', 'always'],
+ 'no-trailing-spaces': 'error',
+ 'object-curly-spacing': ['error', 'always'],
+ 'array-bracket-spacing': ['error', 'never'],
+ 'computed-property-spacing': ['error', 'never'],
+ 'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }],
+ indent: ['error', 2],
+ 'linebreak-style': ['error', 'unix'],
+ eqeqeq: ['error', 'always', { null: 'ignore' }],
+ 'new-cap': ['error', { newIsCap: true, capIsNew: false, properties: true }],
+ 'array-callback-return': ['error', { allowImplicit: false, checkForEach: false }],
+
+ // React rules
+ 'react/react-in-jsx-scope': 'off',
+ 'react/no-unescaped-entities': 'error',
+ 'react-hooks/rules-of-hooks': 'error',
+ 'react-hooks/exhaustive-deps': 'warn',
+
+ // Import rules
+ 'import-x/export': 'error',
+ 'import-x/no-deprecated': 'error',
+ 'import-x/no-empty-named-blocks': 'error',
+ 'import-x/no-extraneous-dependencies': 'error',
+ 'import-x/no-mutable-exports': 'error',
+ 'import-x/no-named-as-default': 'error',
+ 'import-x/no-named-as-default-member': 'error',
+ 'import-x/no-amd': 'error',
+ 'import-x/no-commonjs': 'error',
+ 'import-x/no-nodejs-modules': 'off',
+ 'import-x/default': 'error',
+ 'import-x/named': 'error',
+ 'import-x/namespace': 'error',
+ 'import-x/no-absolute-path': 'error',
+ 'import-x/no-cycle': 'error',
+ 'import-x/no-dynamic-require': 'error',
+ 'import-x/no-internal-modules': 'off',
+ 'import-x/no-relative-packages': 'error',
+ 'import-x/no-self-import': 'error',
+ 'import-x/no-unresolved': ['error', { ignore: ['react'] }],
+ 'import-x/no-useless-path-segments': 'error',
+ 'import-x/no-webpack-loader-syntax': 'error',
+ 'import-x/consistent-type-specifier-style': 'error',
+ 'import-x/exports-last': 'off',
+ 'import-x/extensions': ['error', 'never', { json: 'always' }],
+ 'import-x/first': 'error',
+ 'import-x/group-exports': 'off',
+ 'import-x/newline-after-import': 'error',
+ 'import-x/no-anonymous-default-export': 'off',
+ 'import-x/no-default-export': 'off',
+ 'import-x/no-duplicates': 'error',
+ 'import-x/no-named-default': 'error',
+ 'import-x/no-namespace': 'error',
+ 'import-x/no-unassigned-import': ['error', { allow: ['**/*.css'] }],
+ 'import-x/order': [
+ 'error',
+ {
+ groups: [
+ 'builtin',
+ 'external',
+ 'internal',
+ 'parent',
+ 'sibling',
+ 'index',
+ 'object',
+ 'type',
+ ],
+ 'newlines-between': 'always',
+ alphabetize: { order: 'asc', caseInsensitive: true },
+ distinctGroup: true,
+ },
+ ],
+ 'import-x/prefer-default-export': 'off',
+ 'import-x/no-unused-modules': 'error',
+ 'import-x/no-import-module-exports': 'error',
+ 'import-x/unambiguous': 'off',
+ 'import-x/no-relative-parent-imports': [
+ 'error',
+ { ignore: ['#[src,types,root,components,utils,assets]/*'] },
+ ],
+
+ // Promise rules
+ 'promise/catch-or-return': 'error',
+ 'promise/no-return-wrap': 'error',
+ 'promise/param-names': 'error',
+ 'promise/always-return': 'error',
+ 'promise/no-native': 'off',
+ 'promise/no-nesting': 'warn',
+ 'promise/no-promise-in-callback': 'warn',
+ 'promise/no-callback-in-promise': 'warn',
+ 'promise/avoid-new': 'warn',
+ 'promise/no-new-statics': 'error',
+ 'promise/no-return-in-finally': 'warn',
+ 'promise/valid-params': 'warn',
+ 'promise/prefer-await-to-callbacks': 'error',
+ 'promise/no-multiple-resolved': 'error',
+
+ // Security and other rules
+ 'no-catch-all/no-catch-all': 'error',
+ },
+ },
+
+ // TypeScript configs (applied after main config)
+ ...tseslint.configs.recommended,
+ ...tseslint.configs.strict,
+ ...tseslint.configs.stylistic,
+
+ // TypeScript type-checking configuration
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ ...tseslint.configs.recommendedTypeChecked,
+ ...tseslint.configs.strictTypeChecked,
+ ...tseslint.configs.stylisticTypeChecked,
+ ],
+ languageOptions: {
+ parserOptions: {
+ project: ['./tsconfig.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+ rules: {
+ '@typescript-eslint/consistent-type-imports': 'error',
+ '@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
+ 'no-void': ['error', { allowAsStatement: true }],
+
+ // Disable empty function rule - legitimate use in React contexts
+ '@typescript-eslint/no-empty-function': 'off',
+
+ // Configure no-unused-expressions to allow logical AND and ternary patterns
+ '@typescript-eslint/no-unused-expressions': [
+ 'error',
+ {
+ allowShortCircuit: true,
+ allowTernary: true,
+ },
+ ],
+ },
+ },
+
+ // JSON files configuration
+ {
+ files: ['**/*.json'],
+ plugins: {
+ json: jsonPlugin,
+ },
+ rules: {
+ // Disable TypeScript-specific rules for JSON files
+ '@typescript-eslint/no-unused-expressions': 'off',
+ // JSON-specific rules
+ 'json/*': 'error',
+ },
+ },
+
+ // CommonJS configuration files (e.g., postcss.config.cjs)
+ // Provides Node.js globals (module, require, etc.) that were previously
+ // included via eslint-config-standard in ESLint v8
+ {
+ files: ['**/*.cjs'],
+ languageOptions: {
+ globals: {
+ ...globals.node,
+ },
+ sourceType: 'commonjs',
+ },
+ },
+
+ // Prettier recommended config (should be last to override other formatting rules)
+ eslintPluginPrettierRecommended,
+)
diff --git a/lib/package.json b/lib/package.json
index 1f560316..8b6c6cfe 100644
--- a/lib/package.json
+++ b/lib/package.json
@@ -26,7 +26,7 @@
"scripts": {
"build": "rollup -c",
"start": "rollup -c -w",
- "test:lint:eslint": "eslint --ext .ts,.tsx,.js,.jsx,.cjs,.mjs,.json,.yml,.yaml --max-warnings 0 .",
+ "test:lint:eslint": "eslint --max-warnings 0 .",
"lint": "npm run test:lint:eslint",
"lintfix": "npm run test:lint:eslint -- --fix",
"test:component": "cypress run --component --browser electron",
@@ -44,6 +44,7 @@
"license": "GPL-3.0-only",
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "^4.4.1",
+ "@eslint/js": "^9.36.0",
"@rollup/plugin-alias": "^6.0.0",
"@rollup/plugin-commonjs": "^29.0.0",
"@rollup/plugin-node-resolve": "^16.0.3",
@@ -56,19 +57,15 @@
"@types/leaflet.markercluster": "^1.5.5",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.0.5",
- "@typescript-eslint/eslint-plugin": "^5.62.0",
- "@typescript-eslint/parser": "^5.62.0",
"@vitejs/plugin-react": "^4.3.4",
"@vitest/coverage-v8": "^3.0.5",
"cypress": "^15.7.1",
- "daisyui": "^5.5.5",
- "eslint": "^8.24.0",
+ "daisyui": "^5.5.14",
+ "eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
- "eslint-config-standard": "^17.1.0",
"eslint-import-resolver-typescript": "^4.4.4",
- "eslint-plugin-import": "^2.31.0",
- "eslint-plugin-json": "^3.1.0",
- "eslint-plugin-n": "^17.23.1",
+ "eslint-plugin-import-x": "^4.16.1",
+ "eslint-plugin-json": "^4.0.1",
"eslint-plugin-no-catch-all": "^1.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-promise": "^7.2.1",
@@ -76,22 +73,23 @@
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"eslint-plugin-security": "^3.0.1",
- "eslint-plugin-yml": "^1.14.0",
+ "globals": "^16.3.0",
"happy-dom": "^20.0.11",
"postcss": "^8.4.21",
- "prettier": "^3.7.3",
+ "prettier": "^3.7.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"rollup": "^4.53.3",
- "rollup-plugin-dts": "^6.1.1",
+ "rollup-plugin-dts": "^6.3.0",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-svg": "^2.0.0",
- "tailwindcss": "^4.1.17",
+ "tailwindcss": "^4.1.18",
"typedoc": "^0.27.6",
"typedoc-plugin-coverage": "^3.4.1",
"typedoc-plugin-missing-exports": "^3.1.0",
"typescript": "^5.9.3",
- "vite": "^7.2.4",
+ "typescript-eslint": "^8.9.0",
+ "vite": "^7.3.0",
"vite-plugin-svgr": "^4.3.0",
"vitest": "^3.0.5"
},
@@ -103,16 +101,16 @@
"dependencies": {
"@heroicons/react": "^2.0.17",
"@maplibre/maplibre-gl-leaflet": "^0.1.3",
- "@tiptap/core": "^3.11.0",
- "@tiptap/extension-bubble-menu": "^3.11.0",
- "@tiptap/extension-color": "^3.11.0",
- "@tiptap/extension-image": "^3.11.0",
- "@tiptap/extension-link": "^3.11.0",
- "@tiptap/extension-placeholder": "^3.11.0",
- "@tiptap/extension-youtube": "^3.11.0",
+ "@tiptap/core": "^3.13.0",
+ "@tiptap/extension-bubble-menu": "^3.13.0",
+ "@tiptap/extension-color": "^3.13.0",
+ "@tiptap/extension-image": "^3.13.0",
+ "@tiptap/extension-link": "^3.13.0",
+ "@tiptap/extension-placeholder": "^3.13.0",
+ "@tiptap/extension-youtube": "^3.13.0",
"@tiptap/pm": "^3.6.5",
- "@tiptap/react": "^3.11.0",
- "@tiptap/starter-kit": "^3.11.0",
+ "@tiptap/react": "^3.13.0",
+ "@tiptap/starter-kit": "^3.13.0",
"axios": "^1.13.2",
"browser-image-compression": "^2.0.2",
"classnames": "^2.5.1",
@@ -128,12 +126,12 @@
"react-leaflet": "^4.2.1",
"react-leaflet-cluster": "^3.1.1",
"react-markdown": "^9.0.1",
- "react-photo-album": "^3.2.1",
+ "react-photo-album": "^3.3.0",
"react-qr-code": "^2.0.16",
"react-toastify": "^9.1.3",
"remark-breaks": "^4.0.0",
"tiptap-markdown": "^0.9.0",
- "yet-another-react-lightbox": "^3.21.7"
+ "yet-another-react-lightbox": "^3.26.0"
},
"imports": {
"#assets/*": "./src/assets/*",
diff --git a/lib/postcss.config.cjs b/lib/postcss.config.cjs
index 6bb9c83c..4d0b5bd1 100644
--- a/lib/postcss.config.cjs
+++ b/lib/postcss.config.cjs
@@ -1,4 +1,4 @@
-// eslint-disable-next-line import/no-commonjs
+// eslint-disable-next-line import-x/no-commonjs
module.exports = {
plugins: {
'@tailwindcss/postcss': {},
diff --git a/lib/setupTest.ts b/lib/setupTest.ts
index 3ed9ed03..0f35e917 100644
--- a/lib/setupTest.ts
+++ b/lib/setupTest.ts
@@ -1,2 +1,2 @@
-// eslint-disable-next-line import/no-unassigned-import
+// eslint-disable-next-line import-x/no-unassigned-import
import '@testing-library/jest-dom'
diff --git a/lib/src/Components/AppShell/NavBar.tsx b/lib/src/Components/AppShell/NavBar.tsx
index 51f521b9..17eecc01 100644
--- a/lib/src/Components/AppShell/NavBar.tsx
+++ b/lib/src/Components/AppShell/NavBar.tsx
@@ -31,7 +31,9 @@ export default function NavBar({ appName }: { appName: string }) {
className='tw:btn tw:btn-square tw:btn-ghost tw:ml-3'
aria-controls='#sidenav'
aria-haspopup='true'
- onClick={() => toggleSidebar()}
+ onClick={() => {
+ toggleSidebar()
+ }}
>
@@ -50,7 +52,9 @@ export default function NavBar({ appName }: { appName: string }) {
diff --git a/lib/src/Components/AppShell/SideBar.tsx b/lib/src/Components/AppShell/SideBar.tsx
index 4c7d6799..1e8a1747 100644
--- a/lib/src/Components/AppShell/SideBar.tsx
+++ b/lib/src/Components/AppShell/SideBar.tsx
@@ -6,7 +6,7 @@ import SidebarSubmenu from './SidebarSubmenu'
export interface Route {
path: string
- icon: JSX.Element
+ icon: React.JSX.Element
name: string
submenu?: Route[]
blank?: boolean
@@ -57,7 +57,9 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
to={`${route.path}${params && '?' + params.toString()}`}
className={({ isActive }) =>
- `${isActive ? 'tw:font-semibold tw:bg-base-200 tw:rounded-none!' : 'tw:font-normal tw:rounded-none!'}`
+ isActive
+ ? 'tw:font-semibold tw:bg-base-200 tw:rounded-none!'
+ : 'tw:font-normal tw:rounded-none!'
}
onClick={() => {
if (screen.width < 640 && !appState.sideBarSlim) toggleSidebarOpen()
@@ -70,7 +72,7 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
{route.icon}
{route.name}
@@ -119,7 +121,7 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
>
{route.icon}
{route.name}
@@ -143,7 +145,9 @@ export function SideBar({ routes, bottomRoutes }: { routes: Route[]; bottomRoute
'tw:w-5 tw:h-5 tw:mb-4 tw:mr-5 tw:mt-2 tw:cursor-pointer tw:float-right tw:delay-400 tw:duration-500 tw:transition-all ' +
(!appState.sideBarSlim ? 'tw:rotate-180' : '')
}
- onClick={() => toggleSidebarSlim()}
+ onClick={() => {
+ toggleSidebarSlim()
+ }}
/>
diff --git a/lib/src/Components/AppShell/SidebarSubmenu.tsx b/lib/src/Components/AppShell/SidebarSubmenu.tsx
index 347d1323..a5f39ea0 100644
--- a/lib/src/Components/AppShell/SidebarSubmenu.tsx
+++ b/lib/src/Components/AppShell/SidebarSubmenu.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/prefer-find */
import ChevronDownIcon from '@heroicons/react/24/outline/ChevronDownIcon'
import { useEffect, useState } from 'react'
import { Link, useLocation } from 'react-router-dom'
@@ -10,7 +11,7 @@ function SidebarSubmenu({
icon,
}: {
path: string
- icon: JSX.Element
+ icon: React.JSX.Element
name: string
submenu?: Route[]
}) {
@@ -31,7 +32,12 @@ function SidebarSubmenu({
return (
{/** Route header */}
-
setIsExpanded(!isExpanded)}>
+
{
+ setIsExpanded(!isExpanded)
+ }}
+ >
{icon}
{name}
setEmail(e.target.value)}
+ onChange={(e) => {
+ setEmail(e.target.value)
+ }}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
setPassword(e.target.value)}
+ onChange={(e) => {
+ setPassword(e.target.value)
+ }}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
diff --git a/lib/src/Components/Auth/RequestPasswordPage.tsx b/lib/src/Components/Auth/RequestPasswordPage.tsx
index dcfb416f..713516c1 100644
--- a/lib/src/Components/Auth/RequestPasswordPage.tsx
+++ b/lib/src/Components/Auth/RequestPasswordPage.tsx
@@ -28,7 +28,7 @@ export function RequestPasswordPage({ resetUrl }: { resetUrl: string }) {
},
error: {
render({ data }) {
- return `${data as string}`
+ return data as string
},
},
pending: 'sending email ...',
@@ -42,7 +42,9 @@ export function RequestPasswordPage({ resetUrl }: { resetUrl: string }) {
type='email'
placeholder='E-Mail'
value={email}
- onChange={(e) => setEmail(e.target.value)}
+ onChange={(e) => {
+ setEmail(e.target.value)
+ }}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
diff --git a/lib/src/Components/Auth/SetNewPasswordPage.tsx b/lib/src/Components/Auth/SetNewPasswordPage.tsx
index c8f59591..fc00649c 100644
--- a/lib/src/Components/Auth/SetNewPasswordPage.tsx
+++ b/lib/src/Components/Auth/SetNewPasswordPage.tsx
@@ -28,7 +28,7 @@ export function SetNewPasswordPage() {
},
error: {
render({ data }) {
- return `${data as string}`
+ return data as string
},
},
pending: 'setting password ...',
@@ -41,7 +41,9 @@ export function SetNewPasswordPage() {
setPassword(e.target.value)}
+ onChange={(e) => {
+ setPassword(e.target.value)
+ }}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
diff --git a/lib/src/Components/Auth/SignupPage.tsx b/lib/src/Components/Auth/SignupPage.tsx
index 1eb51ea9..13c0089f 100644
--- a/lib/src/Components/Auth/SignupPage.tsx
+++ b/lib/src/Components/Auth/SignupPage.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { toast } from 'react-toastify'
@@ -32,7 +33,7 @@ export function SignupPage() {
},
error: {
render({ data }) {
- return `${data as string}`
+ return data as string
},
autoClose: 10000,
},
@@ -61,20 +62,26 @@ export function SignupPage() {
type='text'
placeholder='Name'
value={userName}
- onChange={(e) => setUserName(e.target.value)}
+ onChange={(e) => {
+ setUserName(e.target.value)
+ }}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
setEmail(e.target.value)}
+ onChange={(e) => {
+ setEmail(e.target.value)
+ }}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
setPassword(e.target.value)}
+ onChange={(e) => {
+ setPassword(e.target.value)
+ }}
className='tw:input tw:input-bordered tw:w-full tw:max-w-xs'
/>
diff --git a/lib/src/Components/Auth/useAuth.tsx b/lib/src/Components/Auth/useAuth.tsx
index 413e88f5..fe332d80 100644
--- a/lib/src/Components/Auth/useAuth.tsx
+++ b/lib/src/Components/Auth/useAuth.tsx
@@ -67,7 +67,7 @@ export const AuthProvider = ({ userApi, children }: AuthProviderProps) => {
return undefined
}
// eslint-disable-next-line no-catch-all/no-catch-all
- } catch (error) {
+ } catch {
setLoading(false)
return undefined
} finally {
@@ -135,7 +135,8 @@ export const AuthProvider = ({ userApi, children }: AuthProviderProps) => {
setLoading(true)
try {
await userApi.requestPasswordReset(email, resetUrl)
- return setLoading(false)
+ setLoading(false)
+ return
} catch (error) {
setLoading(false)
throw error
@@ -146,7 +147,8 @@ export const AuthProvider = ({ userApi, children }: AuthProviderProps) => {
setLoading(true)
try {
await userApi.passwordReset(token, newPassword)
- return setLoading(false)
+ setLoading(false)
+ return
} catch (error) {
setLoading(false)
throw error
diff --git a/lib/src/Components/Gaming/Quests.tsx b/lib/src/Components/Gaming/Quests.tsx
index 944a2633..35d512b6 100644
--- a/lib/src/Components/Gaming/Quests.tsx
+++ b/lib/src/Components/Gaming/Quests.tsx
@@ -44,7 +44,9 @@ export function Quests() {
diff --git a/lib/src/Components/Gaming/hooks/useQuests.tsx b/lib/src/Components/Gaming/hooks/useQuests.tsx
index 1b3cc1d4..6539a008 100644
--- a/lib/src/Components/Gaming/hooks/useQuests.tsx
+++ b/lib/src/Components/Gaming/hooks/useQuests.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/no-empty-function */
import { useCallback, useState, createContext, useContext } from 'react'
type UseQuestManagerResult = ReturnType
diff --git a/lib/src/Components/Input/Autocomplete.tsx b/lib/src/Components/Input/Autocomplete.tsx
index 2bb97d2c..ae9be9c7 100644
--- a/lib/src/Components/Input/Autocomplete.tsx
+++ b/lib/src/Components/Input/Autocomplete.tsx
@@ -85,7 +85,9 @@ export const Autocomplete = ({
ref={inputRef}
{...inputProps}
type='text'
- onChange={(e) => handleChange(e)}
+ onChange={(e) => {
+ handleChange(e)
+ }}
tabIndex='-1'
onKeyDown={handleKeyDown}
className='tw:border-none tw:focus:outline-none tw:focus:ring-0 tw:mt-5'
@@ -94,7 +96,12 @@ export const Autocomplete = ({
className={`tw:absolute tw:z-4000 ${filteredSuggestions.length > 0 && 'tw:bg-base-100 tw:rounded-xl tw:p-2'}`}
>
{filteredSuggestions.map((suggestion, index) => (
- handleSuggestionClick(suggestion)}>
+ {
+ handleSuggestionClick(suggestion)
+ }}
+ >
))}
diff --git a/lib/src/Components/Input/TextEditorMenu.tsx b/lib/src/Components/Input/TextEditorMenu.tsx
index 6bf6184e..8642cdae 100644
--- a/lib/src/Components/Input/TextEditorMenu.tsx
+++ b/lib/src/Components/Input/TextEditorMenu.tsx
@@ -16,14 +16,14 @@ export const TextEditorMenu = ({ editor }: { editor: Editor }) => {
selector: (ctx) => {
return {
isBold: ctx.editor.isActive('bold'),
- canBold: ctx.editor.can().chain().focus().toggleBold().run(),
+ canBold: ctx.editor.can().toggleBold(),
isItalic: ctx.editor.isActive('italic'),
- canItalic: ctx.editor.can().chain().focus().toggleItalic().run(),
+ canItalic: ctx.editor.can().toggleItalic(),
isStrike: ctx.editor.isActive('strike'),
- canStrike: ctx.editor.can().chain().focus().toggleStrike().run(),
+ canStrike: ctx.editor.can().toggleStrike(),
isCode: ctx.editor.isActive('code'),
- canCode: ctx.editor.can().chain().focus().toggleCode().run(),
- canClearMarks: ctx.editor.can().chain().focus().unsetAllMarks().run(),
+ canCode: ctx.editor.can().toggleCode(),
+ canClearMarks: ctx.editor.can().unsetAllMarks(),
isParagraph: ctx.editor.isActive('paragraph'),
isHeading1: ctx.editor.isActive('heading', { level: 1 }),
isHeading2: ctx.editor.isActive('heading', { level: 2 }),
@@ -36,8 +36,8 @@ export const TextEditorMenu = ({ editor }: { editor: Editor }) => {
isOrderedList: ctx.editor.isActive('orderedList'),
isCodeBlock: ctx.editor.isActive('codeBlock'),
isBlockquote: ctx.editor.isActive('blockquote'),
- canUndo: ctx.editor.can().chain().focus().undo().run(),
- canRedo: ctx.editor.can().chain().focus().redo().run(),
+ canUndo: ctx.editor.can().undo(),
+ canRedo: ctx.editor.can().redo(),
}
},
})
@@ -77,7 +77,7 @@ export const TextEditorMenu = ({ editor }: { editor: Editor }) => {
-
+
{
-
+
{
-
+
{/*
@@ -135,7 +135,7 @@ export const TextEditorMenu = ({ editor }: { editor: Editor }) => {
-
+
handleLayerClick(layer)}
+ onClick={() => {
+ handleLayerClick(layer)
+ }}
onTouchEnd={(e) => {
handleLayerClick(layer)
e.preventDefault()
diff --git a/lib/src/Components/Map/Subcomponents/Controls/FilterControl.tsx b/lib/src/Components/Map/Subcomponents/Controls/FilterControl.tsx
index e5c6c340..03a6f8ce 100644
--- a/lib/src/Components/Map/Subcomponents/Controls/FilterControl.tsx
+++ b/lib/src/Components/Map/Subcomponents/Controls/FilterControl.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable array-callback-return */
import FunnelIcon from '@heroicons/react/24/outline/FunnelIcon'
import { useEffect, useState } from 'react'
@@ -18,7 +19,9 @@ export function FilterControl() {
]
useEffect(() => {
- groupTypes.map((layer) => addVisibleGroupType(layer.value))
+ groupTypes.map((layer) => {
+ addVisibleGroupType(layer.value)
+ })
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
@@ -48,7 +51,9 @@ export function FilterControl() {
>
toggleVisibleGroupType(groupType.value)}
+ onChange={() => {
+ toggleVisibleGroupType(groupType.value)
+ }}
type='checkbox'
className='tw:checkbox tw:checkbox-xs tw:checkbox-success'
checked={isGroupTypeVisible(groupType.value)}
diff --git a/lib/src/Components/Map/Subcomponents/Controls/LayerControl.tsx b/lib/src/Components/Map/Subcomponents/Controls/LayerControl.tsx
index 83a1a29d..6e40bd07 100644
--- a/lib/src/Components/Map/Subcomponents/Controls/LayerControl.tsx
+++ b/lib/src/Components/Map/Subcomponents/Controls/LayerControl.tsx
@@ -36,7 +36,9 @@ export function LayerControl({ expandLayerControl = false }: { expandLayerContro
>
toggleVisibleLayer(layer)}
+ onChange={() => {
+ toggleVisibleLayer(layer)
+ }}
type='checkbox'
className='tw:checkbox tw:checkbox-xs tw:checkbox-success tw:text-white'
checked={isLayerVisible(layer)}
diff --git a/lib/src/Components/Map/Subcomponents/Controls/LocateControl.spec.tsx b/lib/src/Components/Map/Subcomponents/Controls/LocateControl.spec.tsx
index 8bfbfd5c..909a0238 100644
--- a/lib/src/Components/Map/Subcomponents/Controls/LocateControl.spec.tsx
+++ b/lib/src/Components/Map/Subcomponents/Controls/LocateControl.spec.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable camelcase */ // Directus database fields use snake_case
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
diff --git a/lib/src/Components/Map/Subcomponents/Controls/LocateControl.tsx b/lib/src/Components/Map/Subcomponents/Controls/LocateControl.tsx
index 536ee3e5..f0907f81 100644
--- a/lib/src/Components/Map/Subcomponents/Controls/LocateControl.tsx
+++ b/lib/src/Components/Map/Subcomponents/Controls/LocateControl.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable camelcase */ // Directus database fields use snake_case
+/* eslint-disable promise/always-return */
import { control } from 'leaflet'
import { useCallback, useEffect, useRef, useState } from 'react'
import SVG from 'react-inlinesvg'
@@ -15,7 +17,7 @@ import DialogModal from '#components/Templates/DialogModal'
import type { Item } from '#types/Item'
import type { LatLng } from 'leaflet'
-// eslint-disable-next-line import/no-unassigned-import
+// eslint-disable-next-line import-x/no-unassigned-import
import 'leaflet.locatecontrol'
// Type definitions for leaflet.locatecontrol
@@ -31,7 +33,7 @@ declare module 'leaflet' {
* React wrapper for leaflet.locatecontrol that provides user geolocation functionality
* @category Map Controls
*/
-export const LocateControl = (): JSX.Element => {
+export const LocateControl = (): React.JSX.Element => {
const map = useMap()
const myProfile = useMyProfile()
const updateItem = useUpdateItem()
@@ -206,7 +208,9 @@ export const LocateControl = (): JSX.Element => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
if (lc) lc.stop()
// Reset flag after a delay to allow future updates
- setTimeout(() => setHasUpdatedPosition(false), 5000)
+ setTimeout(() => {
+ setHasUpdatedPosition(false)
+ }, 5000)
} catch (error: unknown) {
if (error instanceof Error) {
toast.update(toastId, {
@@ -278,7 +282,9 @@ export const LocateControl = (): JSX.Element => {